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