I am trying to show a list of side-scrolling list in iOS. Some of the lists show but others don't, even though they all have data. I put a debug point and see that the ForEach with EventItemView is called for every section. I am unsure of what I am doing wrong.
EventScreen
struct EventScreen: View {
#State
var currentPage: Int = 0
var viewControllers =
IncentiveSource().getFeaturedIncentives().map({ incentive in
UIHostingController(rootView: EventFeatureView(event: incentive.toEvent()))
})
var response: [EventSection] = IncentiveSource().getIncentives().sections.toEventSections()
var body: some View {
NavigationView {
List {
EventViewController(controllers: self.viewControllers, currentPage: self.$currentPage)
.listRowInsets(EdgeInsets())
.frame(height: 600)
ForEach(self.response) { section in
EventSectionView(eventSection: section)
}
}
.navigationBarTitle(Text("Events").foregroundColor(Color.black), displayMode: .inline)
}
}
}
EventSectionView
struct EventSectionView: View {
var eventSection: EventSection
var body: some View {
VStack(alignment: .leading) {
SectionTextView(text: eventSection.category.typeName())
.frame(alignment: .leading)
ScrollView(.horizontal, showsIndicators: true) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.eventSection.events) { event in
EventItemView(event: event)
}
}
}
}
}
}
struct SectionTextView: View {
var text: String
var body: some View {
return Text(text)
.bold()
.font(.system(size: 18, weight: .heavy))
.foregroundColor(Color(ColorTheme.brandBlue.value))
.padding(.bottom, 4)
}
}
EventItemView
struct EventItemView: View {
var event: Event
var body: some View {
VStack {
Color.red
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(5)
Text(event.title)
.bold()
.frame(width: 100, alignment: .leading)
.foregroundColor(.white)
.font(.system(size: 10))
Text(event.date)
.frame(width: 100, alignment: .leading)
.foregroundColor(.white)
.font(.system(size: 10))
}
.padding(.trailing, 8)
}
}
It needs to make each horizontal scroller in EventSectionView unique like below
struct EventSectionView: View {
var eventSection: EventSection
var body: some View {
VStack(alignment: .leading) {
SectionTextView(text: eventSection.category.typeName())
.frame(alignment: .leading)
ScrollView(.horizontal, showsIndicators: true) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.eventSection.events) { event in
EventItemView(event: event)
}
}
}.id(UUID().uuidString() // << unique
}
}
}
Related
This is my main view where i'm using AsyncAwait to fetch the data. I wanna have the effect of nice animation when i receive the data. Tried a lot of things, but none worked. Some animations were successful but only after opening the specific view for the 3-rd + time. On first opening i could never achieve that effect. Would really appreciate if someone could tell me what should i do. Thanks in advance :)
struct PresentationRootView: View {
// setting up the default value for segmented control
#State private var selectedSegmentedOption: SegmentedSelection = .myPresentations
#ObservedObject var viewModel: PresentationsViewModel
var body: some View {
let columns = [GridItem(.flexible())]
VStack(spacing: 0) {
Picker(selection: $selectedSegmentedOption) {
Text("My Presentations").tag(SegmentedSelection.myPresentations)
Text("Templates").tag(SegmentedSelection.templates)
} label: {}
.pickerStyle(.segmented)
}
ScrollView {
if selectedSegmentedOption == .myPresentations {
if viewModel.shouldShowEmptyScreen {
Text("No data")
}
LazyVGrid(columns: columns, spacing: 4, content: {
ForEach($viewModel.myPresentations, id: \.ID) { element in
PresentationSingleView(url: element.ThumbURLWithSas.wrappedValue, title: element.Name.wrappedValue, numberOfResources: 4)
.padding([.leading, .trailing], 13)
}
}).task {
await viewModel.getAllPresentations(page: 1, pageSize: 10)
}
}
}
.background(CustomColor.background.swiftUIColor)
Spacer()
.navigationTitle("Presentations")
}
}
struct PresentationSingleView: View {
// replace this with the real data
var url: URL
var title: String
var numberOfResources: Int
var isIphone = DefaultAppManager.shared.isIphone
#StateObject var image = FetchImage()
var body: some View {
HStack(alignment: .top) {
image.view?
.resizable()
.scaledToFill()
.frame(maxWidth: isIphone ? 90 : 140, maxHeight: isIphone ? 64 : 140)
.cornerRadius(4)
.padding(isIphone ? 10 : 20)
VStack(alignment: .leading) {
Text(title)
.font(Font.custom("Roboto-Regular", size: 12))
.padding(.top, 20)
Spacer()
HStack(alignment: .center) {
Image("presentationResourceIcon")
Text("\(numberOfResources) resources")
.font(Font.custom("Roboto-Medium", size: 12))
Spacer()
Image("more_options")
.onTapGesture {
print("tapped on right image!!!")
}
}
.padding(.bottom, 20)
}
Spacer()
}
.onAppear {
image.priority = .normal
image.load(url)
}
.frame(height: isIphone ? 90 : 150)
.background(Color.white)
.cornerRadius(8)
}
}
First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.
I'm creating a home screen with a list of custom components. These componentes are one or more click areas with navigation link or buttons, like the above image:
App Home
But the click area is not respected, if I click in any space in the row the click is triggered even it's outside the button/navigation link area
Click buttom area
Click bug video
The code is available in https://github.com/isaquedev/swiftui-list-click-bug and bellow:
Home
import SwiftUI
struct ContentView: View {
var items: [HomeSection] = [
HomeSection(title: "Mais próximos", type: .nearest),
HomeSection(title: "", type: .list, items: [
SectionItem(title: "Românticos", cover: "prato-1"),
SectionItem(title: "Especiais", cover: "prato-2"),
SectionItem(title: "Agradáveis", cover: "prato-1"),
SectionItem(title: "Interessantes", cover: "prato-2"),
])
]
var body: some View {
VList {
ForEach(items) { item in
switch item.type {
case .nearest:
NearestCell(title: item.title)
case .list:
ListCell(items: item.items ?? [])
}
}
}
}
}
VList
import SwiftUI
struct VList<Content:View>: View {
var content: () -> Content
var body: some View {
if #available(iOS 14.0, *) {
ScrollView {
LazyVStack(spacing: 0) {
content()
}
}
} else {
List {
content()
.padding(.horizontal, -20)
}
.onAppear {
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().selectionStyle = .none
}
}
}
}
NearestCell
import SwiftUI
struct NearestCell: View {
var title: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.title)
.padding(.bottom, 16)
VStack {
Text("Veja restaurantes a sua volta")
Button(action: {
print("Click")
}, label: {
Text("Visualizar")
})
.padding(.top, 16)
}
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .center)
.padding(.vertical, 16)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.black, lineWidth: 1)
)
}
.padding(.all, 16)
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
}
}
ListCell
import SwiftUI
struct ListCell: View {
var items: [SectionItem]
var body: some View {
VStack(alignment: .leading) {
Text("Categorias")
.font(.title)
ScrollView(.vertical, showsIndicators: false) {
HStack {
ForEach(items) { item in
VStack {
Image(item.cover)
.resizable()
.scaledToFill()
.frame(width: 80, height: 80, alignment: .center)
.clipped()
Text(item.title)
}
}
}
}
.frame(height: 113)
}
.padding(.all, 16)
}
}
Home Section
enum HomeSectionType {
case nearest, list
}
class HomeSection: Identifiable {
let id = UUID()
var title: String
var type: HomeSectionType
var items: [SectionItem]?
init(title: String, type: HomeSectionType, items: [SectionItem]? = nil) {
self.title = title
self.type = type
self.items = items
}
}
SectionItem
class SectionItem: Identifiable {
let id = UUID()
var title: String
var cover: String
init(title: String, cover: String) {
self.title = title
self.cover = cover
}
}
There is no problem with your code, it is all because of List, List apply action of a Button of a row to all row, for solving the issue use just Text and put action code inside onTapGesture.
struct NearestCell: View {
var title: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.title)
.padding(.bottom, 16)
VStack {
Text("Veja restaurantes a sua volta")
Text("Visualizar") // <<: here
.foregroundColor(Color.blue)
.padding(.top, 16)
.onTapGesture { print("Click") } // <<: here
}
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .center)
.padding(.vertical, 16)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.black, lineWidth: 1)
)
}
.padding(.all, 16)
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
}
}
I would like to display the details of a tourist destination when a destination is selected. Below is the syntax that I created, I call self.presenter.getDetail(request: destination.id) which is in .onAppear, when the program starts and I press a destination, xcode says that self.presenter.detailDestination!.like doesn't exist or nil. Even when I insert print ("TEST") what happens is error nil from self.presenter.detailDestination!.like
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State private var showingAlert = false
#ObservedObject var presenter: GetDetailPresenter<
Interactor<String, DestinationDomainModel, GetDetailDestinationRepository<
GetDestinationLocaleDataSource, GetDetailDestinationRemoteDataSource,
DetailDestinationTransformer>>,
Interactor<String, DestinationDomainModel, UpdateFavoriteDestinationRepository<
FavoriteDestinationLocaleDataSource, DetailDestinationTransformer>>>
var destination: DestinationDomainModel
var body: some View {
ZStack {
if presenter.isLoading {
loadingIndicator
} else {
ZStack {
GeometryReader { geo in
ScrollView(.vertical) {
VStack {
self.imageCategory
.padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(width: geo.size.width, height: 270)
self.content
.padding()
}
}
}
.edgesIgnoringSafeArea(.all)
.padding(.bottom, 80)
VStack {
Spacer()
favorite
.padding(EdgeInsets.init(top: 0, leading: 16, bottom: 10, trailing: 16))
}
}
}
}
.onAppear {
self.presenter.getDetail(request: destination.id)
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
extension DetailView {
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "arrow.left.circle.fill")
.aspectRatio(contentMode: .fill)
.foregroundColor(.black)
Text("Back")
.foregroundColor(.black)
}
}
}
var spacer: some View {
Spacer()
}
var loadingIndicator: some View {
VStack {
Text("Loading...")
ActivityIndicator()
}
}
var imageCategory: some View {
WebImage(url: URL(string: self.destination.image))
.resizable()
.indicator(.activity)
.transition(.fade(duration: 0.5))
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 270, alignment: .center)
.clipShape(RoundedCorner(radius: 30, corners: [.bottomLeft, .bottomRight]))
}
var header: some View {
VStack(alignment: .leading) {
Text("\(self.presenter.detailDestination!.like) Peoples Like This")
.padding(.bottom, 10)
Text(self.presenter.detailDestination!.name)
.font(.largeTitle)
.bold()
.padding(.bottom, 5)
Text(self.presenter.detailDestination!.address)
.font(.system(size: 18))
.bold()
Text("Coordinate: \(self.presenter.detailDestination!.longitude), \(self.presenter.detailDestination!.latitude)")
.font(.system(size: 13))
}
}
var favorite: some View {
Button(action: {
self.presenter.updateFavoriteDestination(request: String(self.destination.id))
self.showingAlert.toggle()
}) {
if self.presenter.detailDestination!.isFavorite == true {
Text("Remove From Favorite")
.font(.system(size: 20))
.bold()
.onAppear {
self.presenter.getDetail(request: destination.id)
}
} else {
Text("Add To Favorite")
.font(.system(size: 20))
.bold()
}
}
.alert(isPresented: $showingAlert) {
if self.presenter.detailDestination!.isFavorite == true {
return Alert(title: Text("Info"), message: Text("Destination Has Added"),
dismissButton: .default(Text("Ok")))
} else {
return Alert(title: Text("Info"), message: Text("Destination Has Removed"),
dismissButton: .default(Text("Ok")))
}
}
.frame(width: UIScreen.main.bounds.width - 32, height: 50)
.buttonStyle(PlainButtonStyle())
.foregroundColor(Color.white)
.background(Color.red)
.cornerRadius(12)
}
var description: some View {
VStack(alignment: .leading) {
Text("Description")
.font(.system(size: 17))
.bold()
.padding(.bottom, 7)
Text(self.presenter.detailDestination!.placeDescription)
.font(.system(size: 15))
.multilineTextAlignment(.leading)
.lineLimit(nil)
.lineSpacing(5)
}
}
var content: some View {
VStack(alignment: .leading, spacing: 0) {
header
.padding(.bottom)
description
}
}
}
onAppear is called during the first render. That means that any values referred to in the view hierarchy (detailDestination in this case) will be rendered during this pass -- not just after onAppear.
In your header, you refer to self.presenter.detailDestination!.like. On the first render, there is not a guarantee that onAppear will have completed it's actions before you force unwrap detailDestination
The simplest solution to this is probably to only conditionally render the rest of the view if detailDestination exists. It looks like you're already trying to do this with isLoading, but there must be a mismatch of states -- my guess is before isLoading is even set to true.
So, your content view could be something like:
if self.presenter.detailDestination != nil {
VStack(alignment: .leading, spacing: 0) {
header
.padding(.bottom)
description
}
} else {
EmptyView()
}
This is all assuming that your presenter has a #Published property that will trigger a re-render of your current component when detailDestination is actually loaded.
As continue research of reverted List in SwiftUI How to make List reversed in SwiftUI.
Getting strange spacing in reverted list, which looks like extra UITableView header/footer.
struct ContentView: View {
#State private var ids = ["header", "test2"]
#State private var text = "text"
init() {
UITableView.appearance().tableFooterView = UIView()
UITableView.appearance().separatorStyle = .none
}
var body: some View {
ZStack (alignment: .bottomTrailing) {
VStack {
List {
ForEach(ids, id: \.self) { id in
Group {
if (id == "header") {
VStack {
Text("Test")
.font(.largeTitle)
.fontWeight(.heavy)
Text("header")
.foregroundColor(.gray)
}
.scaleEffect(x: 1, y: -1, anchor: .center)
} else {
Text(id).scaleEffect(x: 1, y: -1, anchor: .center)
}
}
}
}
.scaleEffect(x:
1, y: -1, anchor: .center)
.padding(.bottom, -8)
Divider()
VStack(alignment: .leading) {
HStack {
Button(action: {}) {
Image(systemName: "photo")
.frame(width: 60, height: 40)
}
TextField("Message...", text: $text)
.frame(minHeight: 40)
Button(action: {
self.ids.insert(self.text, at:0 )
}) {
Image(systemName: "paperplane.fill")
.frame(width: 60, height: 40)
}
}
.frame(minHeight: 50)
.padding(.top, -13)
.padding(.bottom, 50)
}
.foregroundColor(.secondary)
}
}
}
}
Looks not critical but in my more complicated code it shows more spacing.
Ok, turns out it is another SwiftUI bug.
To get around it, you should add .offset(x: 0, y: -1) to the List.
Working example:
struct ContentView: View {
#State private var ids = ["header", "test2"]
#State private var text = ""
init() {
UITableView.appearance().tableFooterView = UIView()
UITableView.appearance().separatorStyle = .none
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack (alignment: .bottomTrailing) {
VStack {
List(ids, id: \.self) { id in
Group {
if (id == "header") {
VStack(alignment: .leading) {
Text("Test")
.font(.largeTitle)
.fontWeight(.heavy)
Text("header").foregroundColor(.gray)
}
} else {
Text(id)
}
}.scaleEffect(x: 1, y: -1, anchor: .center)
}
.offset(x: 0, y: -1)
.scaleEffect(x: 1, y: -1, anchor: .center)
.background(Color.red)
Divider()
VStack(alignment: .leading) {
HStack {
Button(action: {}) {
Image(systemName: "photo").frame(width: 60, height: 40)
}
TextField("Message...", text: $text)
Button(action: {
self.ids.append(self.text)
}) {
Image(systemName: "paperplane.fill").frame(width: 60, height: 40)
}
}
}
.foregroundColor(.secondary)
}
}
}
}
Note that I changed your code a bit to make it more observable and have less code.