SwiftUI List not propagating touches - ios

Trying to work with SwiftUI and experiencing a weird issue. I have a List of Views that should be clickable to push to a detail view, swipable to delete, and have a couple of interactive elements in the view. The list is more or less set up as follows:
NavigationView {
ZStack(alignment: .top) {
if viewModel.value != nil {
List {
ForEach(viewModel.value!, id: \.address) { model in
VStack {
NavigationLink(destination: AliasDetailView(alias: model) { msg in
self.toastText = msg
self.showCopiedToast = true
}) {
ModelRow(alias: alias) { msg in
self.toastText = msg
self.showCopiedToast = true
}
}
}
}
.onDelete(perform: self.deleteModel)
}
.listStyle(PlainListStyle())
.alert(isPresented: $showDefaultAlert) {
Alert(title: Text("Default Address"),
message: Text("Would you like to set this as your default address?"),
primaryButton: .default(Text("Yes")) {
NSUbiquitousKeyValueStore.default.set(self.targetAddress, forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
print("Stored key")
self.targetAddress = ""
}, secondaryButton: .cancel({
NSUbiquitousKeyValueStore.default.set("NONE", forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
self.targetAddress = ""
}))
}
} else {
Text("Loading...")
}
VStack {
Spacer()
.alert(isPresented: $showInvalidAddressAlert) {
Alert(title: Text("Invalid Email"), message: Text("Please enter a valid address."), dismissButton: .default(Text("Ok")))
}
// Begin Floating action button
HStack {
Spacer()
Button(action: {
addModelAction()
}, label: {
Text("+")
.font(.largeTitle)
.frame(width: 77, height: 70)
.foregroundColor(.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3, x: 3, y: 3)
.alert(isPresented: $showAccountLimitAlert) {
Alert(title: Text("Account Limit Reached"), message: Text("You are currently at your account limit for models. Upgrade your account to create more."), dismissButton: .default(Text("Ok")))
}
}
}
}
.navigationBarTitle("Models")
.navigationBarItems(trailing:
Button(action: {
self.showSettingsModal = true
}) {
Image(systemName: "gearshape")
.font(.title)
}
)
.addPartialSheet(style: PartialSheetStyle(
backgroundColor: .black,
handlerBarColor: .secondary,
enableCover: true,
coverColor: Color.black.opacity(0.6),
blurEffectStyle: .dark))
.popup(isPresented: $showCopiedToast, type: .toast, position: .bottom, autohideIn: 2) {
HStack {
Text(self.toastText)
}
.frame(width: 200, height: 60)
.background(Color(red: 0.85, green: 0.8, blue: 0.95))
.cornerRadius(30.0)
.padding(15)
}
.sheet(isPresented: $showSettingsModal) {
SettingsView()
.environmentObject(ProductsStore.shared)
}
I've included everything in the body because I have a suspicion its related to something else blocking the touches. I've tried removing some things but can't seem to identify it. Here is the view code for the row:
HStack {
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text(model.address)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
Text("➜ \(model.target)")
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
VStack {
Image(systemName: "doc.on.doc")
.frame(width: 35, height: 38)
.font(.headline)
.foregroundColor(.accentColor)
.onTapGesture {
copyAlias()
}
}
}
HStack {
Text("Received: \(model.received)")
Spacer()
Toggle("", isOn: $isActive)
.labelsHidden()
.onReceive([self.isActive].publisher.first()) { value in
if value != prevActive {
toggleModel()
}
}
}
}
}
Also note: The list view does work if you hold the elements. i.e. Tapping a row will not trigger the navigation link but holding the cell will eventually trigger it.

Turns out it was the Toast Library I was using. Likely however their view modifier works its capturing touches.

Related

SwiftUI ScrollView won't let me scroll up

I'm trying to implement a chat feature, the relevant codes are simple enough
struct ContentView: View {
#State private var channelId = ""
#State private var message = ""
#ObservedObject var sig = SignalRService(urlStr: "\(API.HubsEndpoint)/hubs/theHub")
var body: some View {
VStack {
HStack {
TextField("Channel ID", text: $channelId) .textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "JoinRoom", arguments: [channelId]) { error in
print("joined")
if let err = error {
print(err)
}
}
} label: {
Text("Join")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding()
Spacer()
ScrollView {
ScrollViewReader { scrollView in
LazyVStack(alignment: .leading, spacing: 5) {
ForEach(sig.messages) { msg in
HStack(alignment: .top) {
Text(msg.user)
.font(.caption)
.bold()
Text(msg.message)
.font(.caption)
}
.id(msg.id)
.padding(20)
.background(.regularMaterial)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.onChange(of: sig.messages.last) { newValue in
scrollView.scrollTo(newValue?.id)
}
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
.background(Color.black)
HStack {
TextField("Message", text: $message)
.textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "SendMessage", arguments: [message, channelId]) { error in
print("messaged")
if let err = error {
print(err)
}
else {
message = ""
}
}
} label: {
Image(systemName: "paperplane.fill")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding(.horizontal)
}
}
}
And the result: https://imgur.com/a/9JdPkib
It kept snapping to the bottom when I tried to scroll up.
I'm clipping the frame size and increased the padding to exaggerate the views, so it's easier to test.
Any idea what's causing the issue?

SwiftUI: How to remove extra space in lists, when .deleteDisabled(true)?

When the ability to delete is disabled [.deleteDisabled(true)] on a list and the list's edit button [EditButton()] is pressed, the extra space to the left of each list item still appears as if it were still show the delete buttons (I don't want to do multi-selection of list rows either, just organize the list items individually), but I haven't found or been able to remove that space on the left, which belongs to the delete buttons and I want strip to make the text fill the entire line, because the stripping capability still leaves that space even if it's disabled.
List {
ForEach(projectListVM.projects
.filter{filterFavorites ?
$0.isFavorite == true :
($0.isFavorite || !$0.isFavorite)},
id: \.id) { itemProject in
ProjectListRowScreen(itemProject: itemProject, projectListVM: projectListVM)
}
.onMove(perform: moveItem)
.deleteDisabled(true)
.listRowBackground(Color.clear)
}
ProjectListRowScreen.swift
Answering to George: Can you post the code from ProjectListRowScreen?
VStack(alignment: .leading) {
NavigationLink {
ProjectDetailScreen(projectVM: itemProject)
} label: {
VStack(alignment: .leading) {
HStack(alignment: .top) {
if itemProject.isFavorite {
Image(systemName: "star.fill")
.frame(width: 18, alignment: .top)
.foregroundColor(Color(itemProject.color.isEmpty ? tagColors[0] : itemProject.color))//Color("Favorite"))
.padding(.top, paddingMicro)
} else {
Image(systemName: "cube.fill")
.frame(height: 18, alignment: .top)
.foregroundColor(Color(itemProject.color.isEmpty ? tagColors[0] : itemProject.color))
.padding(.top, paddingMicro)
}
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(itemProject.title).fontWeight(.thin)
.multilineTextAlignment(.leading)
}
Spacer()
}
}
}
}
}
.sheet(isPresented: $showEditView,
onDismiss: {projectListVM.fetchAllProjects()},
content: {ProjectEditScreen(projectItem: itemProject)})
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
self.toogleFavorite(projectId: itemProject.id)
} label: {
if itemProject.isFavorite {
Label("Unfavorite", systemImage: "star.slash")
} else {
Label("Favorite", systemImage: "star.fill")
}
}
.tint(itemProject.isFavorite ? .gray: .yellow)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
showConfirmationDelete = true
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
Button {
showEditView = true
} label: {
Label("Edit",systemImage: "square.and.pencil")
}
.tint(.blue)
}
.confirmationDialog(
"Are you sure? Action cannot be undone",
isPresented: $showConfirmationDelete,
titleVisibility: .visible,
actions: {
Button("Delete", role: .destructive) {
withAnimation {
self.deleteProjectById(projectId: itemProject.id)
showConfirmationDelete = false
}
}
Button("Cancel", role: .cancel) { showConfirmationDelete = false }
})
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background(Color("BackgroundPanel"))
.clipShape(RoundedRectangle(cornerRadius: cornerMedium, style: .continuous))

onAppear SwiftUI iOS 14.4

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.

SwiftUI: Why does an EnvironmentalObject get reinitialized when you interact with a TextField in this navigation flow?

In my app you go through the onboarding during sign-up. That works perfectly. However, you can go through it again via the Profile Page.
The profile page code in question looks like this (embedded in a top-level NavigationView of course)
NavigationLink(destination:
EndDateView().environmentObject(OnboardingVM(coordinator: viewModel.appCoordinator))
) {
HStack {
Image(systemName: "chart.pie.fill")
.font(.title)
Text("Edit Spending Plan")
.fontWeight(.bold)
.scaledFont("Avenir", 20)
}
}
.buttonStyle(ButtonWithIcon())
This leads you to a screen where you set a Date via a date picker. All is good at this point.
When you navigate to the third page of this flow (Profile -> Datepicker -> Set Income) and interact with a SwiftUI TextField, the #EnvironmentObject reinitializes itself.
Below you will see the code snippets that show how we navigate/pass the env object around
DatePicker Navigation Code
NavigationLink(destination: SetIncomeView().environmentObject(onboardingVM)) {
PurpleNavigationButton(buttonText: onboardingVM.continueButton)
}
SetIncome Code
struct SetIncomeView: View {
#EnvironmentObject var onboardingVM: OnboardingVM
#ObservedObject var setIncomeVM = SetIncomeVM()
var body: some View {
VStack {
HStack {
CustomHeader1(text: SetIncomeContent.title)
Button(action: {
setIncomeVM.info.toggle()
}) {
Image(systemName: "info.circle")
}
}
.alert(isPresented: $setIncomeVM.info) {
Alert(title: Text(SetIncomeContent.title),
message: Text(SetIncomeContent.header),
dismissButton: .default(Text(SetIncomeContent.dismiss)))
}.padding()
Spacer()
ScrollView {
HStack {
CustomHeader2(text: SetIncomeContent.income)
TextField("", text: $onboardingVM.expectedIncomeAmount)
.keyboardType(.numberPad)
.frame(width: 100, height: 35, alignment: .center)
.scaledFont("Avenir", 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
HStack {
CustomHeader2(text: SetIncomeContent.onBasis)
Picker(selection: $onboardingVM.selectedBasis, label: Text("")) {
ForEach(0 ..< self.onboardingVM.basis.count) {
Text(self.onboardingVM.basis[$0])
}
}
.frame(width: 100)
.clipped()
CustomHeader2(text: SetIncomeContent.basis)
}
Button(action: {
setIncomeVM.otherIncome.toggle()
}) {
if setIncomeVM.otherIncome {
Image(systemName: "minus.circle")
} else {
Image(systemName: "plus.circle")
}
Text(SetIncomeContent.anotherSource)
.foregroundColor(.black)
.underline()
.fixedSize(horizontal: false, vertical: true)
.padding()
}
//TODO: I am getting a bug where if i type or scroll it closes the view below
HStack {
CustomHeader2(text: SetIncomeContent.income)
TextField("", text: $onboardingVM.additionalIncome)
.keyboardType(.numberPad)
.frame(width: 100, height: 35, alignment: .center)
.scaledFont("Avenir", 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.isHidden(!setIncomeVM.otherIncome)
HStack {
CustomHeader2(text: SetIncomeContent.onBasis)
Picker(selection: $onboardingVM.additionalBasis, label: Text("")) {
ForEach(0 ..< self.onboardingVM.basis.count) {
Text(self.onboardingVM.basis[$0])
}
}
.frame(width: 100)
.clipped()
CustomHeader2(text: SetIncomeContent.basis)
}.isHidden(!setIncomeVM.otherIncome)
}
Spacer()
NavigationLink(destination: SetSavingsView().environmentObject(onboardingVM),
isActive: $setIncomeVM.savingsLink1Active) {
Button(action: {
setIncomeVM.savingsLink1Active.toggle()
}) {
Text(SetIncomeContent.noIncome)
.foregroundColor(.black)
.underline()
.fixedSize(horizontal: false, vertical: true)
.padding()
}
}
NavigationLink(destination: SetSavingsView().environmentObject(onboardingVM), isActive: $setIncomeVM.savingsLink2Active) {
Button(action: {
self.onboardingVM.saveIncomeBasis()
if !setIncomeVM.savingsLink2Active {
setIncomeVM.savingsLink2Active.toggle()
}
}) {
PurpleNavigationButton(buttonText: onboardingVM.continueButton)
}
}
}
Spacer()
}
}
Any idea why this happens?

Editing NavigationBarItems and NavigationBarTitle in SwiftUI inside TabView

I'm trying to change the navigation bar title and leading & trailing items in a SwiftUI app.
Here is the situation:
A user logs in, then he gets transferred to a view inside a TabView.
Before login, in the welcome screen and login & register screens I have no navigation bar title, but after login, I want to add the navigation bar items and title.
What is important to know is that my after-login-view is inside a TabView.
If I wrap my after-login-view in a new NavigationView and then change the navigation bar items and title it works! but it shows me 2 navigation bars:
which is not what I want.
here is the code I use :
This is the after-Login-View
import SwiftUI
struct DiscoveryView: View {
var body: some View {
List{
Text("List Items")
}.offset(y: 10)
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(leading:
Text("Discovery").font(Font.custom("Quicksand- Bold", size: 24))
, trailing:
Button(action: {
}) {
HStack{
Text("120").font(Font.custom("Quicksand-Bold", size: 15)).foregroundColor(Color("BlueColor"))
Text("following").font(Font.custom("Quicksand-Bold", size: 15)).foregroundColor(Color.black)
}
}
).navigationBarBackButtonHidden(true)
}
}
This is the TabView:
struct TabController : View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection){
DiscoveryView()
.tabItem {
VStack {
Image(systemName: "list.dash")
Text("Discover")
}
}.tag(1)
ExploreView()
.tabItem {
VStack {
Image(systemName: "square.and.pencil")
Text("Explore")
}
}.tag(2)
SelfProfileView()
.tabItem{
VStack {
Image(systemName: "person.circle")
Text("Profile")
}
}.tag(3)
}.accentColor(Color("BlueColor"))
}
}
This is the code of the login page:
import SwiftUI
struct LoginView: View {
var body: some View {
ZStack{
VStack{
Image("mainLogo").resizable().frame(width: 250, height: 125)
Text("").frame(width: UIScreen.main.bounds.width, height: 100)
HStack{
TextField("Username / Email", text: $email)
.padding(.leading, 10)
.frame(width: 313, height: 49)
.background(RoundedRectangle(cornerRadius: 4).stroke( Color.black.opacity(0.3)).frame(width: 313, height: 39).background(Color("GrayColor")))
}
Text("").frame(width: UIScreen.main.bounds.width, height: 40)
HStack {
HStack{
if self.visable{
TextField("Password", text: $password)
.padding(.horizontal)
} else {
SecureField("Password", text: $password)
.padding(.horizontal)
}
Button(action: {
self.visable.toggle()
}){
Image(systemName: self.visable ? "eye.slash" : "eye")
.padding(.trailing, 20)
.foregroundColor(Color.black)
}
}.background(RoundedRectangle(cornerRadius: 4).stroke( Color.black.opacity(0.3)).frame(width: 313, height: 39).background(Color("GrayColor")) )
}.padding(.horizontal, 40).padding(.vertical)
Text("").frame(width: UIScreen.main.bounds.width, height: 100)
Button(action: {
//pass email password and conpassword to cognito
DSManager().signIn(username: self.email, password: self.password, error: {
error in
if let error = error{
self.error = error.errorString
self.alert.toggle()
print(error.errorString)
}
else{
DSManager().getSelfUserInformation(response: {
userInfo, userCollections,dsError in
if let dsError = dsError{
self.error = dsError.errorString
self.alert.toggle()
print(dsError.errorString)
}
if let userInfo = userInfo{
print("success")
//use userInfo to load stuff for profile page
if let userCollections = userCollections{
self.profileInfo.setProperties(userInfo: userInfo, collections: userCollections)
}
else{
self.profileInfo.setProperties(userInfo: userInfo, collections: [:])
}
self.isComplete.toggle()
}
})
}
})
}) {
Text("Login")
.fontWeight(.semibold)
.frame(width: 313, height: 48)
.background(fillAllFields() ? Color("YellowColor") : Color.gray)
.foregroundColor(Color.white)
.font(.system(size: 17))
.cornerRadius(15)
.shadow(color: Color("GrayColor"), radius: 0, x: 3, y: 3)
}.disabled(!fillAllFields())
NavigationLink(destination: ProfileView(), isActive: $isComplete) {
EmptyView()
}
Spacer()
}
.padding()
.navigationBarTitle("", displayMode: .inline)
if self.alert {
ErrorView(err: $error, alert: $alert)
}
}
}
func fillAllFields() -> Bool {
if self.email == "" || self.password == "" {
return false
}
return true
}
}
As mentioned, I'm trying to edit bar items and title of a view inside and tab view .
Thanks!
So as per your updated question, I added DiscoveryView Inside NavigationView and it worked as expected. I have added code and screenshot as well.
struct TabController : View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection) {
NavigationView {
DiscoveryView()
}
.tabItem {
VStack {
Image(systemName: "list.dash")
Text("Discover")
}
}.tag(1)
Text("Explore View")
.tabItem {
VStack {
Image(systemName: "square.and.pencil")
Text("Explore")
}
}.tag(2)
Text("SelfProfileView")
.tabItem{
VStack {
Image(systemName: "person.circle")
Text("Profile")
}
}.tag(3)
}.accentColor(Color("BlueColor"))
}}

Resources