swiftxcodeuser-interfaceswiftuiswiftui-navigationview

Geometry View Embeded inside NavigationView


I have been designing this view according to this YouTube video https://youtu.be/ytRim2TSdyY?list=PLyJ4OqMEvBQwxJGWIdV6y0VbeKVGkDbCT, with some little difference :

  1. This view comes under the navigationview so view becomes like this enter image description here
  2. so there is extra space between "Top News" and navigation bar
  3. i tried to remove that headline and workaround with numbers in the code but cant able to work out thats why seeking out for guidance in my ios journey as junior developer

Below is the code i have write this is somewhat different than code written in youtube adding the code for reference

struct allArticlesListView: View {
    
    @State var title = "Sports"
    @Environment(\.colorScheme) var colorScheme
    @State var isGotData = false
//    let fetchData = FetchNews()
    @State var articles : [Article] = []
    
    
    
    var body: some View {
        
        if isGotData {
            
            ScrollView(.vertical){
                
                VStack(spacing : 0){
                    
                    VStack(alignment: .leading,spacing: 15) {
                        HStack{
                            Text("TOP NEWS")
                                .font(.largeTitle.bold())
                                .frame(height: 45)
                                .padding(.horizontal,15)
                        }
                        
                        GeometryReader{
                            
                            let rect = $0.frame(in: .scrollView(axis : .vertical))
                            let minY = rect.minY
                            let topValue : CGFloat = 75.0
                            
                            let offset = min(minY - topValue, 0)
                            let progress = max(min( -offset / topValue, 1), 0)
                            let scale = 1 + progress
                            
                            
                            ZStack{
                                Rectangle()
                                    .fill(Color.blue)
                                    .overlay(alignment: .leading) {
                                        Circle()
                                            .fill(Color.blue)
                                            .overlay {
                                                Circle()
                                                    .fill(.white.opacity(0.2))
                                                
                                            }
                                            .scaleEffect(2,anchor: .topLeading)
                                            .offset(x: -50,y: -40)
                                    }
                                    .clipShape(RoundedRectangle(cornerRadius: 25,style: .continuous))
                                    .scaleEffect(scale,anchor: .bottom)
                                
                                VStack(alignment: .center, spacing: 4){
                                    Text("\(title)")
                                        .font(.title.bold())
                                }
                                .foregroundStyle(.white)
                                .frame(maxWidth: .infinity, alignment : .leading)
                                .padding(15)
                                .offset(y : progress * -25 )
                                
                            }
                            .offset(y : -offset)
                            //Moving till top value
                            .offset(y : progress * -topValue)
                            
                        }
                        .padding(.horizontal,15)
                        .containerRelativeFrame(.horizontal)
                        .frame(height: 125)

                        LazyVStack(spacing: 15) {
                            ForEach(articles, id: \.self) { article in
                                NavigationLink(destination: detailedArticleView(singleArticle: article)) {
                                    VStack(alignment: .leading) {
                                        ArticleView(article: article, category: title)
                                    }
                                }
                            }


                        }
                        .padding(15)
                        .mask{
                            Rectangle()
                                .visualEffect { content, geometryProxy in
                                    content
                                        .offset(y : backgroundLimitOffset(geometryProxy))
                                }
                        }
                        .background{
                            GeometryReader {
                                
                                let rect = $0.frame(in: .scrollView)
                                let minY = min(rect.minY - 125, 0)
                                let progress = max(min(-minY / 25, 1), 0)
                                
                                RoundedRectangle(cornerRadius: 30 * progress,style: .continuous)
                                    .fill(colorScheme == .dark ? .black : .white)
                                    .overlay(alignment: .top){
                                        
                                    }
                                /// Limiting Background Scroll below the header
                                    .visualEffect { content, geometryProxy in
                                        content
                                            .offset(y : backgroundLimitOffset(geometryProxy))
                                    }
                                
                            }
                        }
                        
                    }
                    .padding(.vertical,15)
                    
                }
                
            }
            .scrollTargetBehavior(CustomScrollBehaviourSample())
            .scrollIndicators(.hidden)
            .background {
                LinearGradient(colors: [.green, .cyan, .cyan ,.indigo], startPoint: .topLeading, endPoint: .bottomTrailing)
                    .ignoresSafeArea()
            }
        }else{
            
            ProgressView()
                .onAppear {
                    //                    fetchData.fetchData(category: title) { allArticles in
                    //                        self.articles = allArticles
                    //                        print("fetched the data")
                    //                        isGotData = true
                    //                    }
                    fetch()
                    
                }
            
        }
        
        
    }
    
    
    /// Background Limit Offset
    func backgroundLimitOffset(_ proxy : GeometryProxy) -> CGFloat {
        
        let minY = proxy.frame(in: .scrollView).minY
        
        return minY < 75 ? -minY + 75 : 0
        
    }
    
    func fetch(){
        
        if let filePath = Bundle.main.url(forResource: "dummy.json", withExtension: ""){
            if let data = try? Data(contentsOf: filePath){
                if let content = try? JSONDecoder().decode(Welcome.self, from: data){
                    articles = content.articles
                    isGotData = true
                }else{
                    print("error in data decoding")
                }
            }else{
                print("error in data fetching")
            }
        }else{
            print("error in file path")
        }
        
    }
    
}

/// Custom Scroll Target Behaviour
/// AKA scrollWillEndDragging in UIKit

struct CustomScrollBehaviourSample : ScrollTargetBehavior {
    
    func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
        if target.rect.minY < 75 {
            target.rect = .zero
        }
    }
    
}

Solution

  • I managed to reproduce this problem by taking your code and replacing the articles with plain colors. I saw from your screenshot that there was a "Back" button, so I tried using allArticlesListView as the destination for a NavigationLink.

    The problem is seen if the parent view (with the NavigationStack) has a large title. The reason is because, space is being reserved for the title at the top of the detail view.

    To fix, apply .navigationBarTitleDisplayMode(.inline) to the detail view. It's probably a good idea to clear the navigation title at the same time:

    NavigationStack {
        NavigationLink("Show list") {
            allArticlesListView()
                .navigationTitle("")
                .navigationBarTitleDisplayMode(.inline)
        }
        .navigationBarTitleDisplayMode(.large)
    }
    

    Alternatively, you can move these modifiers into the body of the view itself (put them at the end).

    BTW, normal convention is for the names of structs and classes to begin with uppercase. So I would suggest changing it to AllArticlesListView.