I have a VStack with image and a text, I need to make it clickable so I can show and alert to the user. I have tried onTapGesture method but I'm able to print the statement but alert is not showing up.
Is there any alternate way to get the solution?
I have added the whole swift file for your reference
Code
#State private var hasOneConnected: Bool = false
#State private var showConnectionAlert = false
private var connectionAlert: Alert {
Alert.init(title: Text("Oops!"), message: Text("There is an error."), dismissButton: .default(Text("OK")))
}
var body: some View {
VStack(alignment: .center, spacing: 0) {
// MARK: - Header
VStack(alignment: .center, spacing: 0) {
Spacer().frame(height: 55)
HStack(alignment: .center, spacing: 25) {
Spacer()
Image("Logo")
Spacer(minLength: 5)
Text("Zone Control")
.foregroundColor(.white)
Spacer()
}
Spacer()
}
.frame(height: 100, alignment: .center)
.background(
LinearGradient(
gradient: Gradient(colors: [Color.Heat.primary, Color.Heat.primary.opacity(0.8), Color.Heat.primary.opacity(0.5)]),
startPoint: .top, endPoint: .bottom
)
)
// MARK: - Menu Bar
HStack(alignment: .center, spacing: 10) {
Spacer().frame(maxWidth: 20)
HStack(alignment: .center, spacing: 10) {
Image("batteryModule")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 8)
Text("Pod Status")
.font(.caption)
.foregroundColor(.white)
}
Spacer()
VStack(alignment: .center, spacing: 4) {
Image(self.hasConnected ? "bluetoothConnected" : "bluetoothNotConnected")
Text(self.hasConnected ? "Connected" : "Disconnected")
.font(.caption)
.foregroundColor(.white)
}
.frame(width: 80)
VStack(alignment: .center, spacing: 4) {
Image("batteryStatus")
Text("\(self.batteryLevel)%")
.font(.caption)
.foregroundColor(.white)
}
.frame(width: 60)
Spacer().frame(maxWidth: 10)
}
.padding()
.background(Color.black)
}
.statusBar(hidden: true)
.edgesIgnoringSafeArea(.all)
.onAppear(perform: {
UserDefaults.standard.set(true, forKey: "onboarding")
self.update()
})
.onReceive(self.skiinBLE.objectWillChange) { _ in
self.update()
}
}
func update() {
self.hasOneConnected = self.topLayer.cbPeripheral?.state != .disconnected
self.batteryLevel = self.topLayer.moduleInformation?.batteryLevel.rawValue ?? 0
}
For making empty containers tappable (for example Spacer()), you should use .contentShape(Rectangle()) modifier:
VStack(spacing: 4) {
Image(systemName: "antenna.radiowaves.left.and.right")
Text("Connected")
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
print("The whole VStack is tappable now!")
}
I have simplified your code to just bare essentials. You don't really need to add a tap gesture, you can wrap the whole element (e.g. a VStack) in a Button and handle triggering the alert from there. Important bit is to remember to set showConnectionAlert back to false when the user taps OK on the Alert. The side effect of wrapping it in a Button is that everything inside will be rendered in the tint colour. That's why I have applied .foregroundCololor() to the VStack (with some images you might also have to add .renderingMode(.original) modifier):
struct ContentView: View {
#State private var showConnectionAlert = false
var body: some View {
Button(action: { self.showConnectionAlert = true }) {
VStack(spacing: 4) {
Image(systemName: "antenna.radiowaves.left.and.right")
Text("Connected")
} .foregroundColor(.primary)
}
.alert(isPresented: $showConnectionAlert) {
Alert(title: Text("Nice"),
message: Text("The alert is showing!"),
dismissButton: Alert.Button.default(Text("OK"),
action: { self.showConnectionAlert = false }))
}
}
}
Related
I'm trying to go to my SwiftUi View File Home by clicking my button in iOS 16:
I already read Apple documentation and searched in Google and YouTube but I didn't got the answer.
Here is my code:
import SwiftUI
import CoreData
struct ContentView: View {
var body: some View {
VStack (alignment: .leading) {
Text("Welcome To").font(.system(size: 45)).fontWeight(.heavy).foregroundColor(.primary)
Text("Pumping Fitness").font(.system(size: 45)).fontWeight(.heavy).gradientForeground(colors: [.red, .yellow])
Spacer()
VStack (alignment: .leading, spacing: 24) {
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "dumbbell.fill").resizable().frame(width: 40, height: 30).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Track your workouts").bold().font(.system(size: 22)).padding(.top, 10.0)
Text("Easily track your progress during you are working out").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "timer").resizable().frame(width: 40, height: 40).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Auto rest timer").bold().font(.system(size: 22))
Text("Start your rest time with one single tap").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "figure.run").resizable().frame(width: 40, height: 50).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Add your own exercises").bold().font(.system(size: 22))
Text("Create your own type of exercises at a glance").font(.subheadline)
}
}
}
Spacer()
Spacer()
//HStack creado para poder alinear el boton al centro.
HStack(alignment: .center) {
Button(action: {} ) {
Text("Start Pumping").fontWeight(.black).foregroundColor(.white)
}
.padding()
.frame(width: 280, height: 60)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.yellow]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(17)
}.padding(.leading)
}.padding(.all, 40)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
extension View {
public func gradientForeground(colors: [Color]) -> some View {
self.overlay(LinearGradient(gradient: .init(colors: colors), startPoint: .topLeading, endPoint: .topTrailing))
.mask(self)
}
}
Do you know how can I do it? All the YouTube videos I saw were using a list, and I want to show this "welcome page" then go to my home page.
You need to refactor your code a bit. Use ContentView as your module for navigationStack. Separate ContentView code to WelcomeView and use it as Follows:
struct WelcomeView: View {
#Binding var gotoSomewhere: Bool // I recommend you to read some articles about #Sate and #Binding property wrappers
var body: some View {
VStack (alignment: .leading) {
Text("Welcome To").font(.system(size: 45)).fontWeight(.heavy).foregroundColor(.primary)
Text("Pumping Fitness").font(.system(size: 45)).fontWeight(.heavy).gradientForeground(colors: [.red, .yellow])
Spacer()
VStack (alignment: .leading, spacing: 24) {
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "dumbbell.fill").resizable().frame(width: 40, height: 30).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Track your workouts").bold().font(.system(size: 22)).padding(.top, 10.0)
Text("Easily track your progress during you are working out").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "timer").resizable().frame(width: 40, height: 40).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Auto rest timer").bold().font(.system(size: 22))
Text("Start your rest time with one single tap").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "figure.run").resizable().frame(width: 40, height: 50).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Add your own exercises").bold().font(.system(size: 22))
Text("Create your own type of exercises at a glance").font(.subheadline)
}
}
}
Spacer()
Spacer()
//HStack creado para poder alinear el boton al centro.
HStack(alignment: .center) {
Button(action: {
print("Tap Tap")
gotoSomewhere = true
} ) {
Text("Start Pumping").fontWeight(.black).foregroundColor(.white)
}
.padding()
.frame(width: 280, height: 60)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.yellow]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(17)
}.padding(.leading)
}.padding(.all, 40)
}
}
Then update your contentView with NavigationStack like this:
import SwiftUI
import CoreData
struct ContentView: View {
#State var gotoHomePage: Bool = false // this is important
var body: some View {
NavigationStack {
VStack {
WelcomeView(gotoSomewhere: $gotoHomePage)
NavigationLink(isActive: $gotoHomePage) {
HomeView() // This is your Home View you want to navigate to
.navigationBarBackButtonHidden(true) // if you want a back button then pass false or comment this modifier
} label: {
EmptyView()
}
}
.navigationBarHidden(true)
}
}
}
Hope this helps.
If you're looking to use a navigation stack, then you'd want to wrap your View body inside a NavigationView. If you're targeting iOS 16+ you should use NavigationStack instead (https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types). Hacking with Swift also has an article on NavigationStack.
There is some more useful info on NavigationView on the Hacking with Swift blog here, as well as other resources you should be able to find online. There are some examples there you could use in your situations such as:
struct ContentView: View {
#State private var isShowingDetailView = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Second View"), isActive: $isShowingDetailView) { EmptyView() }
Button("Tap to show detail") {
self.isShowingDetailView = true
}
}
.navigationTitle("Navigation")
}
}
}
This question is pretty similar: How to show NavigationLink as a button in SwiftUI
I am making a notification feed where you can view the WHOLE comment a user posts on a blog but I am unable to get the whole multi-line comment to show without affecting the spacing/alignment of elements to match the other notifications.
I was able to make the whole comment show by typing in a constant .frame(height: 100) modifier but then EVERY comment notification has that sized of frame.
Is there a way to make the below VStack scale it's frame dynamically based on the comment length or is there a better way based on my code??
Thank you!
struct CommentNotificationCell: View {
#EnvironmentObject var session: SessionStore
var activity: CommentActivity?
var body: some View {
HStack(spacing: 10) {
if let activity = activity {
VStack {
KFImage(URL(string: "\(url)" )!)
Spacer()
}
// Should I scale this VStack's frame height dynamically based
// on the length of the comment..?
VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 3) {
Text(activity.username)
.font(.caption)
Text("commented on your post:")
.font(.caption)
}
Text(activity.comment)
.font(.caption)
Text(activity.createdAt.timeAgoDisplay())
.font(.caption)
}.padding(.leading, 10)
Spacer()
VStack {
ZStack {
Image(systemName: "bubble.left.fill")
.font(.system(size: 13, weight: .regular))
.foregroundColor(.blue)
.zIndex(1)
.offset(x: -25,y: -20)
KFImage(URL(string: "\(url)" )!)
.zIndex(0)
}
Spacer()
}
}
}
}
}
I think your code is already doing that – scaling dynamically based on text length. I cleaned it up a little and for me it works. Is there some other place where you set a specific .frame?
struct ContentView: View {
#State private var test = false
let username = "funnytest"
let shortComment = "kjfdglkj ewoirewoi oikdjsfgdlsfgj 0ewiropsdisg"
let longComment = "kjfdglkj ewoirewoi oikdjsfgdlsfgj 0ewiropsdisg poksaf#ldsifsgali oeirpo dgiodfig odfi ofdgifgpoüi fhfdi mfoidgho miohgfm ogifhogif hiogfh fgihi oogihofgi hofgiho fgopihfgoih pfdgihdfg podfgihofgiho po fdgdfiopugiouü"
var body: some View {
VStack {
CommentNotificationCell(username: username, comment: shortComment)
CommentNotificationCell(username: username, comment: longComment)
}
}
}
struct CommentNotificationCell: View {
var username: String
var comment: String
var body: some View {
HStack(spacing: 10) {
Image(systemName: "person.circle")
.resizable().scaledToFit().frame(width: 60)
VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 3) {
Text(username)
.font(.caption).bold()
Text("commented on your post:")
.font(.caption)
}
Text(comment)
.font(.caption)
Text("8 hours ago")
.font(.caption)
}.padding(.leading, 10)
Spacer()
ZStack {
Image(systemName: "bubble.left.fill")
.font(.system(size: 13, weight: .regular))
.foregroundColor(.blue)
.zIndex(1)
.offset(x: -25,y: -20)
// Image("URL(string: "\(url)" )!")
// .zIndex(0)
}
}
}
}
I found out the fix was to remove the Spacer() in the first VStack
VStack {
KFImage(URL(string: "\(url)" )!)
}
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.
I have a swiftUI app. The app uses a scrollView LazyVGrid to present 3-photos wide by x rows. I am using the KingFisher Kingfisher package to download images. The performance of the app suffers as the cancelOnDisapper doesn't seem to be called. Thus, if the customer scrolls to the bottom of the 300+ photo list, the customer is required to wait until all 300+ photos are loaded to see the picture.
Whether I use .cancelOnDisappear(true) or not doesn't seem to make a difference.
It doesn't seem to matter where I put the .cancelOnDisappear(true) in the sequence of method calls.
I'm using the cancelOnDisappear method, but when I scroll down, every picture renders in the scrollView even if I scroll quickly. The onSuccess and onFailure methods don't seem to be called either as I am not seeing the debug statements from those calls.
Here is a snippit of the code:
var body: some View {
ZStack {
// specifics note set yet or calculating sale summary
if (!seriesList.isSpecificsListSet || isCalculatingSaleSummary) {
GeometryReader { geometry in
ProgressView("Loading...")
.scaleEffect(3, anchor: .center)
.progressViewStyle(CircularProgressViewStyle(tint: .red))
.frame(width: geometry.size.width,
height: geometry.size.height)
.zIndex(1)
}
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).background(Color(.systemGray5))
.opacity(0.75)
.zIndex(1)
}
if isShowPhotos {
VStack {
ScrollView {
HStack(alignment: .center) {
VStack(alignment: .center) {
Text(kNewText)
.fontWeight(.semibold)
.frame(maxHeight: .infinity, alignment: .center)
Text(kLooseText)
.fontWeight(.semibold)
.frame(maxHeight: .infinity, alignment: .center)
}
VStack(alignment: .leading) {
HStack(spacing: 6) {
filterSwitch(specificsTypeTextEnum: .have, filterValue: $new_HaveShow)
filterSwitch(specificsTypeTextEnum: .want, filterValue: $new_WantShow)
filterSwitch(specificsTypeTextEnum: .sell, filterValue: $new_SellShow)
filterSwitch(specificsTypeTextEnum: .order, filterValue: $new_OrderShow)
Spacer()
}
HStack(spacing: 6) {
filterSwitch(specificsTypeTextEnum: .have, filterValue: $loose_HaveShow)
filterSwitch(specificsTypeTextEnum: .want, filterValue: $loose_WantShow)
filterSwitch(specificsTypeTextEnum: .sell, filterValue: $loose_SellShow)
Spacer()
}
}
} // hstack filter switches
SearchBar(searchText: $searchText, isSearching: $isSearching)
// beginning photos
LazyVGrid(columns: [
GridItem(.flexible(minimum: 100), spacing: 8, alignment: .top),
GridItem(.flexible(minimum: 100), spacing: 8, alignment: .top),
GridItem(.flexible(minimum: 100), spacing: 8)
], alignment: .leading, spacing: 9, content: {
switch showContextType {
case .series:
ForEach(showFigures
.filter({(
$0.seriesUniqueId == series.uniqueId && $0.searchString.lowercased().contains(searchText.lowercased()))
|| ($0.seriesUniqueId == series.uniqueId && searchText.isEmpty)}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
case .whatIsNew:
ForEach(showFigures
.filter({
($0.addedDate > isNewAddedDate
&& $0.searchString.lowercased().contains(searchText.lowercased()))
|| ($0.addedDate > isNewAddedDate && searchText.isEmpty)})
.sorted(by: {$0.addedDate == $1.addedDate ? $0.figurePackageName < $1.figurePackageName : $0.addedDate > $1.addedDate}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
case .allFigures:
ForEach(showFigures
.filter({
($0.searchString.lowercased().contains(searchText.lowercased()))
|| (searchText.isEmpty)})
.sorted(by: {$0.figurePackageName < $1.figurePackageName}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
} // end showContent type switch
}) // end alignment & lazy grid
} // end list view
BannerVC().frame(width: 320, height: 50, alignment: .center)
} // end vstack
.navigationBarTitle(series.seriesName)
.navigationBarItems(
trailing: FigureListMenuItems(series: series,
showContextType: showContextType,
filterByPhase: $filterByPhase,
isShowPhotos: $isShowPhotos,
isCalculatingSaleSummary: $isCalculatingSaleSummary)
)
}
}
}
Code for the loaded photos
struct FigurePhoto: View {
#ObservedObject var figure: Figure
#Binding var needsRefresh: Bool
var body: some View {
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
// name photo and specifics
VStack(alignment: .center, spacing: 4) {
// image and specifics
HStack(alignment: .top, spacing: 4) {
VStack(alignment: .center, spacing: 0) {
let image = figure.flickrPhotoString ?? ""
KFImage(URL(string: image))
.resizable()
.onSuccess { r in
#if DEBUG
print("success: \(r)")
#endif
}
.onFailure { error in
#if DEBUG
print("ERROR: Failure in KFImage: \(error.localizedDescription)")
#endif
}
.placeholder {
// Placeholder while downloading.
kMyToyBoxLogoImage
.resizable()
.opacity(0.3)
.scaledToFit()
.cornerRadius(22)
}
.cancelOnDisappear(true) // cancel if scrolled past
.scaledToFit()
.cornerRadius(22)
.overlay(
GeometryReader { geometry in
HStack {
Spacer()
VStack {
if figure.isSpecificsSet {
SpecificsImageOverlay(specificsType: .have,
newCount: figure.publishedSpecifics.new_haveCount,
looseCount: figure.publishedSpecifics.loose_haveCount)
SpecificsImageOverlay(specificsType: .want,
newCount: figure.publishedSpecifics.new_wantCount,
looseCount: figure.publishedSpecifics.loose_wantCount)
SpecificsImageOverlay(specificsType: .sell,
newCount: figure.publishedSpecifics.new_sellCount,
looseCount: figure.publishedSpecifics.loose_sellCount)
SpecificsImageOverlay(specificsType: .order,
newCount: figure.publishedSpecifics.new_orderCount,
looseCount: 0)
}
}
.frame(width: geometry.size.width/6)
}
}
, alignment: .bottom)
Text(figure.figurePackageName)
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.padding(.top, 4)
// convert to strings to avoid commas
Text(figure.series.seriesName)
.font(.system(size: 9, weight: .regular))
.lineLimit(1)
Spacer()
} // end vstack
.onAppear() {
// primary front image use global unique id to avoid random matching numbers _small
figure.fetchFigureImageURL(withTags: figure.figureGlobalUniqueId, withText: "\(figure.figureGlobalUniqueId)\(kPrimaryFrontImageNameSuffix)\(kSmallSuffix)")
} // end vstack on appear
} // end hstack
} // end vstack
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
)
} // end navigation link
} // end body
}
Based on the comment from #Asperi I moved the NavigationLink to an overlay. The performance is significantly improved and the .cancelOnDisappear(true) is functioning.
.overlay(
ZStack {
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
Rectangle()
.hidden()
}
}
)
Here is the full structure code for the PhotoView. I'm not sure if my implementation is ideal.
struct FigurePhoto: View {
#ObservedObject var figure: Figure
#Binding var needsRefresh: Bool
var body: some View {
// name photo and specifics
VStack(alignment: .center, spacing: 4) {
// image and specifics
HStack(alignment: .top, spacing: 4) {
VStack(alignment: .center, spacing: 0) {
let image = figure.flickrPhotoString ?? ""
KFImage(URL(string: image))
.resizable()
.onSuccess { r in
#if DEBUG
print("success: \(r)")
#endif
}
.onFailure { error in
#if DEBUG
print("ERROR: Failure in KFImage: \(error.localizedDescription)")
#endif
}
.placeholder {
// Placeholder while downloading.
kMyToyBoxLogoImage
.resizable()
.opacity(0.3)
.scaledToFit()
.cornerRadius(22)
}
.cancelOnDisappear(true) // cancel if scrolled past
.scaledToFit()
.cornerRadius(22)
.overlay(
GeometryReader { geometry in
HStack {
Spacer()
VStack {
if figure.isSpecificsSet {
SpecificsImageOverlay(specificsType: .have,
newCount: figure.publishedSpecifics.new_haveCount,
looseCount: figure.publishedSpecifics.loose_haveCount)
SpecificsImageOverlay(specificsType: .want,
newCount: figure.publishedSpecifics.new_wantCount,
looseCount: figure.publishedSpecifics.loose_wantCount)
SpecificsImageOverlay(specificsType: .sell,
newCount: figure.publishedSpecifics.new_sellCount,
looseCount: figure.publishedSpecifics.loose_sellCount)
SpecificsImageOverlay(specificsType: .order,
newCount: figure.publishedSpecifics.new_orderCount,
looseCount: 0)
}
}
.frame(width: geometry.size.width/6)
}
}
, alignment: .bottom)
Text(figure.figurePackageName)
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.padding(.top, 4)
// convert to strings to avoid commas
Text(figure.series.seriesName)
.font(.system(size: 9, weight: .regular))
.lineLimit(1)
Spacer()
} // end vstack
.onAppear() {
// primary front image use global unique id to avoid random matching numbers _small
figure.fetchFigureImageURL(withTags: figure.figureGlobalUniqueId, withText: "\(figure.figureGlobalUniqueId)\(kPrimaryFrontImageNameSuffix)\(kSmallSuffix)")
} // end vstack on appear
} // end hstack
} // end vstack
.overlay(
ZStack {
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
Rectangle()
.hidden()
}
}
)
} // end body
}
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
}
}
}