I'm trying to use onTapGesture but doing so makes it nearly impossible to access the list - if I remove onTapGesture it works perfectly
Here's what I'm working with:
struct NavPickerView: View {
#State var selected: Int = -1
#State var cities: [String] = ["New York", "San Diego", "Houston", "Boston"]
#State var selectionStarted: Bool = false
var body: some View {
VStack {
Text("\(selectionStarted ? "SELECTING" : "WAITING")")
NavigationView {
Form {
Section {
Picker(selection: $selected, label: Text("Search for...")) {
Text("Select City").tag(-1)
ForEach(0..<cities.count) {
Text(verbatim: self.cities[$0]).tag($0)
}
}
}
}
.onTapGesture { self.selectionStarted = true }
.navigationBarTitle(Text("Cities"))
}
}
}
}
Any way of running code when an item is selected without making the list nearly inaccessible?
In your case, it seems .onAppear is a better fit:
Form {
Section {
Picker(selection: $selected, label: Text("Search for...")) {
Text("Select City").tag(-1)
ForEach(0..<cities.count) {
Text(verbatim: self.cities[$0]).tag($0)
}
.onAppear { self.selectionStarted = true }
}
}
}
Related
Whenever a message is received from the backend, the app should automatically scroll to the beginning of the message. I cannot find a way for this to work. I've tried the stackoverflow solutions here, here and here to no avail.
Here's my code
struct ContentView: View {
#State private var inputText = ""
#State private var messages: [Message] = []
#State private var showThumbsDownSheet = false
#State private var feedbackText = ""
#State private var showThanksForFeedback = false
var body: some View {
VStack {
NavigationView {
ScrollViewReader { scrollView in
List {
ForEach(messages, id: \.id) { message in
Text(message.text)
if !message.isFromUser && message.isReplyFromBackEnd {
HStack {
ThumbsUpButton(message: message)
Spacer()
ThumbsDownButton(message: message, showThumbsDownSheet: $showThumbsDownSheet, reviewText: $feedbackText)
}
}
}
}
.onChange(of: messages) { value in
scrollView.scrollTo(self.messages.count - 1)
}
}
.navigationTitle("Ask Tais")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
Button(action: { exit(0) }) {
Label("Quit app", systemImage: "")
}
}
label: {
Label("Add", systemImage: "ellipsis.circle")
}
}
}
}
.onAppear {
// Add a "Hello" message to the list of messages when the app is first opened
self.messages.append(Message(id: UUID(), text: "Hello!", isFromUser: false, isReplyFromBackEnd: false, responseId: "0"))
}
.frame(maxHeight: .infinity)
HStack {
TextField("What do you want to say?", text: $inputText)
Button(action: {
self.messages.append(Message(id: UUID(), text: self.inputText, isFromUser: true, isReplyFromBackEnd: false, responseId:"0"))
sendRequest(with: self.inputText) { responseText, id in
self.messages.append(Message(id: UUID(), text: responseText, isFromUser: false, isReplyFromBackEnd: true, responseId: id))
}
self.inputText = ""
}) {
Text("Ask Tais")
}
}
.padding()
}
}
}
ScrollViewReader and scrollTo does not work on List with changing content. You have to use ScrollView instead of List and build your own list design.
I would like to make an ActionSheet, where user could choose if he want to select image from Gallery or take a picture.
However, implementing this I was not able to find a way how PhotosPicker could be called in such context. The code below doesn't work: the item "Choose from gallery" displayed, but no reaction on tapping.
I will highly appreciate any advises.
Thank you!
Button {
displayImageActionSheet = true
} label: {
Image("user_photo_placeholder")
}
.confirmationDialog("Profile picture",
isPresented: $displayImageActionSheet,
titleVisibility: .visible) {
PhotosPicker("Choose from gallery",
selection: $selectedPhotoImage,
matching: .any(of: [.videos, .not(.images)]))
Button("Take picture") {
}
Button("Remove", role: .destructive) {
}
}
You can present it by using photosPicker(isPresented:)
import SwiftUI
import PhotosUI
struct ConfimShowPhotos: View {
#State var showPicker: Bool = false
#State var showDialog: Bool = false
#State var showCamera: Bool = false
#State var selectedItem: PhotosPickerItem? = nil
var body: some View {
Button {
showDialog.toggle()
} label: {
Image(systemName: "photo")
}
.confirmationDialog("Profile Picture", isPresented: $showDialog) {
Button {
showCamera.toggle()
} label: {
Label("Camera", systemImage: "camera")
}
Button {
showPicker.toggle()
} label: {
Label("Gallery", systemImage: "photo.artframe")
}
}
.photosPicker(isPresented: $showPicker, selection: $selectedItem)
.alert("Say Cheese!!", isPresented: $showCamera) {
Text("Mimicking showing camera")
}
}
}
When I tap edit, it will show a delete button (minus icon). When the delete button tapped it will show the orange delete option (as the gif shows). Now I'm trying to reset the state back to the origin (from orange button back to video name and length ) when the Done button is tapped.
I'm trying few options like closures but nothing much.
Any help would be much appreciated!
My child view Video
struct Video: View {
var videoImage : String
var title : String
var duaration : Int
#Binding var deleteActivated : Bool
var body: some View {
HStack {
Image(videoImage)
...
if deleteActivated {
Button(action: {
}) {
ZStack {
Rectangle()
.foregroundColor(.orange)
.cornerRadius(radius: 10, corners: [.topRight, .bottomRight])
Text("Delete")
.foregroundColor(.white)
}
}
} else {
VStack(alignment: .leading){
....
My parent view VideosDirectory
struct VideosDirectory: View {
#State var videos:[DraftVideos] = [
DraftVideos(isSelected: true,title: "Superman workout", duration: 5, imageURL: "test"),
DraftVideos(isSelected: true,title: "Ironman workout", duration: 15, imageURL: "test1"),
DraftVideos(isSelected: true,title: "Ohman workout and long name", duration: 522, imageURL: "test2")
]
init() {
self._deleteActivated = State(initialValue: Array(repeating: false, count: videos.count))
}
#State private var deleteActivated: [Bool] = []
#State private var show = false
// #State private var editing = false
var body: some View {
// VStack {
NavigationView {
ScrollView(.vertical) {
VStack {
ForEach(videos.indices, id: \.self) { i in
HStack {
if self.show {
Button(action: {
withAnimation {
self.deleteActivated[i].toggle()
}
}) {
Image(systemName: "minus.circle.fill")
...
}
}
Video(videoImage: videos[i].imageURL, title: videos[i].title, duaration: videos[i].duration, deleteActivated: $deleteActivated[i])
}
}
}
.animation(.spring())
}
.navigationBarItems(trailing:
HStack {
Button(action: {
self.show.toggle()
}) {
if self.show {
Text("Done")
} else {
Text("Edit")
}
}
})
}
}
}
Provided code is not testable so just an idea:
Button(action: {
self.deleteActivated = Array(repeating: false, count: videos.count)
self.show.toggle()
}) {
or almost the same but as "post-action" in
}
.animation(.spring())
.onChange(of: self.show) { _ in
// most probably condition is not needed here, but is up to you
self.deleteActivated = Array(repeating: false, count: videos.count)
}
In SwiftUI, I set up a 4 screen flow (1>2>3>4) where the user would hit "next" on each to navigate to the next screen - just like a typical push flow in UIKit. Im using "programmatic" NavigationLinks (e.g. isActive parameter) for flexibility. It gets to screen 3, but for some reason hitting next on screen 3 doesn't navigate to screen 4. Can't figure it out.
struct FlowView: View {
#State var navigateToScreen2 = false
#State var navigateToScreen3 = false
#State var navigateToScreen4 = false
var body: some View {
NavigationView {
VStack {
Text("Screen 1")
Button(action: { self.navigateToScreen2 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 2")
Button(action: { self.navigateToScreen3 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 3")
Button(action: { self.navigateToScreen4 = true}, label: { Text("Next") })
NavigationLink(destination:
Text("Screen 4"),
isActive: self.$navigateToScreen4,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen3,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen2,
label: { EmptyView() }
)
}
}
}
}
I would do it like this. It works and can be much better read:
struct ContentView: View {
#State private var navigateToScreen2 = false
var body: some View {
NavigationView {
NavigationLink(destination: View2(), isActive: $navigateToScreen2) {
Text("View1")
}
}
}
}
struct View2: View {
#State private var navigateToScreen3 = false
var body: some View {
NavigationLink(destination: View3(), isActive: $navigateToScreen3) {
Text("View2")
}
}
}
struct View3: View {
#State private var navigateToScreen4 = false
var body: some View {
NavigationLink(destination: View4(), isActive: $navigateToScreen4) {
Text("View3")
}
}
}
struct View4: View {
var body: some View {
Text("View4")
}
}
I defined 2 popovers and one sheet in the View Line().
Using this view in a VStack, everything works fine.
Using it inside a List, the wrong popovers /sheets are displayed when the corresponding text or Button is tapped.
What's going wrong here?
struct ContentView: View {
var body: some View {
VStack {
Line()
List {
Line()
Line()
Line()
}
}
}
}
struct Line: View {
#State private var showPopup1 = false
#State private var showPopup2 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Button("Sheet2"){self.showSheet2 = true}
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}
}
struct Popover1: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 1 \(self.showSheet ? "T" : "F")")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Popover2: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 2")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Sheet2: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Sheet 2")
Button("Cancel"){ self.presentation.wrappedValue.dismiss() }
}
}
}
Just don't use Button for .sheet. List detects buttons in row and activate entire row (not sure about bug, let it be as designed). So using only and for everywhere in sub-elements gestures, makes your code work.
Tested with Xcode 11.2 / iOS 13.2
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Text("Sheet2") // << here !!!
.onTapGesture {self.showSheet2 = true} // << here !!!
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}