0

I have code with SwiftUI, but when I use LazyVStack to optimize performance, my app has a bug, is when I scroll to bottom, and load more data, LazyVStack will don't continue generate View to Screen: example video : [https://drive.google.com/file/d/1FCJQZo71bwyavFp4aCC1gHYENqM4zEYH/view?usp=sharing](Bug with LazyVStack in SwiftUI In Google Drive), I have change color for views to easy look bug, thanks every one. And this my code:

struct HomePage: View {
    @StateObject var viewModel: HomeViewModel
    
    @ViewBuilder var body: some View {
        ZStack {
            switch viewModel.homeData {
            case .`init`:
                loadingContent("init")
                    .onAppear(perform: loadHomePage)
            case .loading:
                loadingContent("loading")
            case .success(let data):
                successContent(data)
            case .error(let error):
                errorContent(error)
            }
        }
        .onChange(of: viewModel.isLoadMore) { newValue in
            print("Change is load more: \(newValue)")
        }
        .onAppear {
            UIScrollView.appearance().bounces = false
        }
    }
}

// MARK: - Layouts
private extension HomePage {
    @ViewBuilder func loadingContent(_ placeHolder: String) -> some View {
        VStack {
            ProgressView()
                .padding(16)
            Text(placeHolder)
        }
    }
    
    @ViewBuilder func successContent(_ data: [HomeData]) -> some View {
        loadAllData {
            LazyVStack {
                LazyVStack {
                    ForEach(data, id: \.name) { section in
                        homeSection(section)
                    }
                }
                .background(Color.red)
                
                Color.gray.frame(height: 16)
            }
            .background(Color.yellow)
        }
    }
    
    @ViewBuilder func loadAllData<Content>(content: () -> Content) -> some View where Content: View {
        if let data = viewModel.homeData.data, let last = data.last, case .anotherArticles = last {
            ScrollView {
                content()
            }
            .background(Color.blue)
            .refreshable {
                loadHomePage()
            }
            .onAppear {
                print("can refresh")
            }
        } else {
            ScrollView {
                content()
            }
            .background(Color.green)
            .onAppear {
                print("can't refresh")
            }
        }
    }
    
    @ViewBuilder func errorContent(_ error: Error) -> some View {
        ScrollView {
            Text(error.localizedDescription)
        }
        .refreshable {
            loadHomePage()
        }
    }
    
    @ViewBuilder func homeSection(_ data: HomeData) -> some View {
        VStack {
            switch data {
            case .featuredArticles(let articles):
                featureArticles(articles)
            case .top10Coin(let assets):
                top10Coin(data.sectionTitle, assets)
            case .trendingArticles(let articles):
                trendingArticles(articles)
            case .video(let video):
                newestVideo(video)
            case .newestArticle(let articles):
                newestArticle(data.sectionTitle, articles)
            case .featuredPodcasts(let podcasts):
                featuredPodcasts(data.sectionTitle, podcasts)
            case .firstAnotherArticle(let article):
                firstAnotherArticle(data.sectionTitle, article)
            case .secondAnotherArticles(let articles):
                secondAnotherArticles(articles)
            case .anotherArticles(let articles):
                anotherArticles(articles)
                    .background(Color.brown)
            }
            
//            if viewModel.isLoadMore {
//                loadingContent("Load more ...")
//                    .frame(height: 80)
//                    .padding(.bottom, 16)
//            } else if let data = viewModel.homeData.data, let last = data.last {
//                if case .anotherArticles(_) = last {
//
//                } else {
//                    loadingContent("Load Performance ...")
//                        .frame(height: 80)
//                        .padding(.bottom, 16)
//                }
//            }
        }
    }
    
    @ViewBuilder func featureArticles(_ articles: [Article]) -> some View {
        ForEach(articles, id: \.id) { article in
            let hArticleItemView = VStack {
                Divider()
                    .background(Color.black)
                    .padding(.bottom, 16)
                
                HorizontalArticleItemView(article: article)
            }
                .padding(.horizontal, 16)
            if article == articles.first {
                VerticalArticleItemView(article: article)
            } else if article == articles.last {
                hArticleItemView
            } else {
                hArticleItemView
                    .padding(.bottom, 16)
            }
        }
    }
    
    @ViewBuilder func top10Coin(_ sectionTitle: String, _ assets: [Asset]) -> some View {
        SectionHeaderView(title: sectionTitle)
            .padding(.horizontal, 16)
            .padding(.top, 16)
        
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack {
                ForEach(assets, id: \.id) { asset in
                    if asset == assets.last {
                        AssetItemView(asset: asset)
                            .padding(.trailing, 16)
                    } else {
                        HStack {
                            AssetItemView(asset: asset)
                                .padding(.trailing, 16)
                            
                            Divider()
                        }
                    }
                }
                .padding(.leading, 16)
            }
        }
    }
    
    @ViewBuilder func trendingArticles(_ articles: [Article]) -> some View {
        VStack {
            ForEach(articles, id: \.id) { article in
                if article == articles.first {
                    TrendingArticleItemView(article: article)
                        .padding(.bottom, 16)
                } else {
                    VStack {
                        Divider()
                            .background(Color.black)
                            .padding(.bottom, 16)
                        
                        TrendingArticleItemView(article: article)
                    }
                    .padding(.top, 16)
                }
            }
            .padding(.horizontal, 16)
        }
        .padding(.vertical, 16)
        .background(Color.gray)
        .padding(16)
    }
    
    @ViewBuilder func newestVideo(_ video: YoutubeVideo) -> some View {
        VStack {
            SectionHeaderView(title: "Video", textColor: .white, dividerColor: .white)
                .padding(.horizontal, 16)
                .padding(.top, 16)
            
            NewestVideoItemView(video: video)
        }
        .background(Color.black)
    }
    
    @ViewBuilder func newestArticle(_ sectionTitle: String, _ articles: [Article]) -> some View {
        SectionHeaderView(title: sectionTitle)
            .padding(.horizontal, 16)
            .padding(.top, 16)
        
        VStack {
            ForEach(articles, id: \.id) { article in
                let hArticleItemView =
                VStack {
                    Divider()
                        .background(Color.black)
                        .padding(.bottom, 16)
                    
                    HorizontalArticleItemView(article: article)
                }
                if article == articles.first {
                    HorizontalArticleItemView(article: article)
                        .padding(.bottom, 16)
                } else if article == articles.last {
                    hArticleItemView
                } else {
                    hArticleItemView
                        .padding(.bottom, 16)
                }
            }
        }
        .padding(.horizontal, 16)
        .padding(.bottom, 16)
    }
    
    @ViewBuilder func featuredPodcasts(_ sectionTitle: String, _ podcasts: [Episode]) -> some View {
        VStack {
            SectionHeaderView(title: sectionTitle, isShowDivider: false)
                .padding(.horizontal, 16)
                .padding(.top, 16)
            
            FeaturedPodcastView(podcasts: podcasts)
        }
        .background(Color.gray)
    }
    
    @ViewBuilder func firstAnotherArticle(_ sectionTitle: String, _ article: Article) -> some View {
        SectionHeaderView(title: "Video")
            .padding(.horizontal, 16)
            .padding(.top, 16)
        
        VerticalArticleItemView(article: article)
            .padding(.bottom, 16)
        
        Divider()
            .background(Color.black)
            .padding(.bottom, 16)
            .padding(.horizontal, 16)
    }
    
    @ViewBuilder func secondAnotherArticles(_ articles: [Article]) -> some View {
        HStack(spacing: 16) {
            if let firstArticle = articles.first {
                VerticalArticleItemView(article: firstArticle, horizontalImagePadding: 16)
            }
            
            if let lastArticle = articles.last {
                VerticalArticleItemView(article: lastArticle, horizontalImagePadding: 16)
            }
        }
    }
    
    @ViewBuilder func anotherArticles(_ articles: ListItemSource<Article>) -> some View {
        LazyVStack(spacing: 16) {
            ForEach(articles.listItem, id: \.id) { article in
                let item = VStack {
                    Divider()
                        .background(Color.black)
                        .padding(.bottom, 16)
                    
                    HorizontalArticleItemView(article: article)
                }
                    .padding(.horizontal, 16)
                
                let canLoadMore: Bool = {
                    if let anotherArticles = viewModel.homeData.data?.last, case .anotherArticles = anotherArticles {
                        let canLoadMore = articles.listItem.count - (articles.listItem.firstIndex(of: article) ?? 0) < 10 && !viewModel.isLoadMore && articles.canLoadMore
                        return canLoadMore
                    } else {
                        return false
                    }
                }()
                
                if canLoadMore {
                    item
                        .onAppear {
                            print(canLoadMore)
                        }
                        .onAppear(perform: loadMore)
                } else if articles.listItem.last == article, !canLoadMore {
                    item
                        .onAppear {
                            print(canLoadMore)
                        }
                        .padding(.bottom, 16)
                } else {
                    item
                        .onAppear {
                            print(canLoadMore)
                        }
                }
            }
        }
    }
}

// MARK: - Actions
private extension HomePage {
    func loadHomePage() {
        viewModel.getHomePage()
    }
    
    func loadMore() {
        guard
            let data = viewModel.homeData.data,
            !data.isEmpty,
            let anotherArticles = data.last,
            case .anotherArticles(articles: let articles) = anotherArticles,
            !articles.listItem.isEmpty,
            articles.canLoadMore,
            !viewModel.isLoadMore else {
            return
        }
        viewModel.isLoadMore = true
        viewModel.loadMoreHomePage()
    }
}

I try use VStack but this case bad performance, I hope continue use LazyVStack

0 Answers0