I am attempting to add a LongPressGesture to a view that acts as a button which brings up a sheet. When it's a Button it works fine and the swipe works between views. But when I remove the button and add a LongPressGesture to the view, only the long press works and you can no longer swipe left and right on the TabView and only swipe above or below the view (example below).
I assume there's some precedence of gestures here, could anyone enlighten me on what the process is to have the TabView swiping gesture work with precedence over the LongPressGesture?
import SwiftUI
struct ContentView: View {
#State private var toggleSheet: Bool = false
#GestureState private var isDetectingLongPress: Bool = false
var body: some View {
TabView {
Text("1")
.font(Font.system(size: 300))
.gesture(
LongPressGesture().updating(self.$isDetectingLongPress) {
currentState, gestu[![enter image description here][1]][1]reState, transaction in
gestureState = currentState
}
.onEnded {
_ in
self.toggleSheet.toggle()
}
)
.sheet(isPresented: self.$toggleSheet) {
Rectangle()
}
Text("2")
.font(Font.system(size: 300))
.gesture(
LongPressGesture().updating(self.$isDetectingLongPress) {
currentState, gestureState, transaction in
gestureState = currentState
}
.onEnded {
_ in
self.toggleSheet.toggle()
}
)
.sheet(isPresented: self.$toggleSheet) {
Circle()
}
}
.tabViewStyle(PageTabViewStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Hard to see in the giphy, but when swiping my mouse is above or below the view.
Related
Since iOS 16 there is a new feature for the ".sheet" modifier called ".presentationDetents". ".presentationDetents" has a parameter called "selection" where you can pass a Binding. You can programmatically resize the sheet with the "selection" parameter. As soon as you change the sheet size for example from PresentationDetent.medium to PresentationDetent.large right after changed the page with a "NavigationLink" the View below gets cut off:
But if I slightly move (resize) the sheet afterwards the cut off below is going to disappear:
The view hierarchy is also strange:
If you add a delay by 0.6s for resizing the sheet, the cut off won't happen.
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
currentSelection = .large
}
}
You can find the code below:
import SwiftUI
struct ContentView: View {
#State private var sheetIsOpened = false
#State private var currentSelection = PresentationDetent.medium
var body: some View {
Text("Click to open a sheet")
.padding()
.onTapGesture {
sheetIsOpened = true
}
.sheet(isPresented: $sheetIsOpened) {
NavigationStack {
List {
ScrollView {
ForEach(0..<100) { index in
VStack {
NavigationLink(destination: NavigatedView(currentSelection: $currentSelection)) {
Text("I have the index: \(index)")
.foregroundColor(.green)
}
}
.frame(maxWidth: .infinity)
}
}
.padding()
}
}
.presentationDetents([.medium, .large], selection: $currentSelection)
}
}
}
struct NavigatedView: View {
#Binding fileprivate var currentSelection: PresentationDetent
var body: some View {
ScrollView {
ForEach(0..<100) { index in
VStack {
Text("I'm a child and I have the index: \(index)")
.onAppear {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
currentSelection = .large
// }
}
}
.frame(maxWidth: .infinity)
}
}
.background(.red)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I am trying to make an effect where the navigation bar and tab bar fade out while the user is scrolling, and then fade in when the user taps on the screen. This is the code I have so far:
import SwiftUI
struct QPageView: View {
#State private var searching = ""
#State private var isScrolling: Visibility = .visible
var body: some View {
List {
ForEach(0..<40) {
Text("\($0)")
}
}
.navigationTitle("Hello")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $searching, placement: .navigationBarDrawer(displayMode: .always))
.toolbar(isScrolling, for: .navigationBar)
.toolbar(isScrolling, for: .tabBar)
.simultaneousGesture(DragGesture().onChanged { _ in
withAnimation {
isScrolling = .hidden
}
})
.simultaneousGesture(TapGesture().onEnded { _ in
withAnimation {
isScrolling = .visible
}
})
}
}
struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView()
}
}
struct PageView: View {
var body: some View {
TabView {
NavigationView {
NavigationLink(destination: QPageView()) {
Text("Click Me")
}
}
.tabItem {
Label("Hey", systemImage: "book.fill")
}
}
}
}
This is the current effect I'm getting. I am getting a sliding animation, which is not the desired effect I am going for.
Anyone know how I can accomplish a fading effect? I cannot seem to figure it out using withAnimation. Any help would be appreciated.
I have two NavigationLink in a cell of my List
I want to go to destination1 when I tap once,and go to destination2 when I tap twice.
So I added two tap gesture to control the navigation.
But when I tap,there are two questions:
1 The tap gesture block won't be called.
2 The two navigation link will be both activated automatically even if they are behind a TextView.
The real effect is: Tap the cell -> go to Destination1-> back to home -> go to Destination2 -> back to home
Here is my code :
struct MultiNavLink: View {
#State var mb_isActive1 = false;
#State var mb_isActive2 = false;
var body: some View {
return
NavigationView {
List {
ZStack {
NavigationLink("", destination: Text("Destination1"), isActive: $mb_isActive1)
NavigationLink("", destination: Text("Destination2"), isActive: $mb_isActive2)
Text("Single tap::go to destination1\nDouble tap,go to destination2")
}
.onTapGesture(count: 2, perform: {()->Void in
NSLog("Double tap::to destination2")
self.mb_isActive2 = true
}).onTapGesture(count: 1, perform: {()->Void in
NSLog("Single tap::to destination1")
self.mb_isActive1 = true
})
}.navigationBarTitle("MultiNavLink",displayMode: .inline)
}
}
}
I have tried remove the List element,then everything goes as I expected.
It seems to be the List element that makes everything strange.
I found this question:SwiftUI - Two buttons in a List,but the situation is different from me.
I am expecting for your answer,thank you very much...
Try the following approach - the idea is to hide links in background of visible content and make them inactive for UI, but activated programmatically.
Tested with Xcode 12 / iOS 14.
struct MultiNavLink: View {
var body: some View {
return
NavigationView {
List {
OneRowView()
}.navigationBarTitle("MultiNavLink", displayMode: .inline)
}
}
}
struct OneRowView: View {
#State var mb_isActive1 = false
#State var mb_isActive2 = false
var body: some View {
ZStack {
Text("Single tap::go to destination1\nDouble tap,go to destination2")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.background(Group {
NavigationLink(destination: Text("Destination1"), isActive: $mb_isActive1) {
EmptyView() }
.buttonStyle(PlainButtonStyle())
NavigationLink(destination: Text("Destination2"), isActive: $mb_isActive2) {
EmptyView() }
.buttonStyle(PlainButtonStyle())
}.disabled(true))
.highPriorityGesture(TapGesture(count: 2).onEnded {
self.mb_isActive2 = true
})
.onTapGesture(count: 1) {
self.mb_isActive1 = true
}
}
}
Navigation link has a initializer that takes a binding selection and whenever that selection is set to the value of the NavigationLink tag, the navigation link will trigger.
As a tip, if the app can't differentiate and identify your taps, and even with two taps, still the action for one-tap will be triggered, then you can use a simultaneous gesture(.simultaneousGesture()) modifier instead of a normal gesture(.gesture()) modifier.
struct someViewName: View {
#State var navigationLinkTriggererForTheFirstOne: Bool? = nil
#State var navigationLinkTriggererForTheSecondOne: Bool? = nil
var body: some View {
VStack {
NavigationLink(destination: SomeDestinationView(),
tag: true,
selection: $navigationLinkTriggererForTheFirstOne) {
EmptyView()
}
NavigationLink(destination: AnotherDestinationView(),
tag: true,
selection: $navigationLinkTriggererForTheSecondOne) {
EmptyView()
}
NavigationView {
Button("tap once to trigger the first navigation link.\ntap twice to trigger the second navigation link.") {
// tap once
self.navigationLinkTriggererForTheFirstOne = true
}
.simultaneousGesture(
TapGesture(count: 2)
.onEnded { _ in
self.navigationLinkTriggererForTheSecondOne = true
}
)
}
}
}
}
I'm struggling to implement TapGesture and LongPressGesture simultaneously in a ScrollView. Everything works fine with .onTapGesture and .onLongPressGesture, but I want that the opacity of the button gets reduced when the user taps on it, like a normal Button().
However, a Button() doesn't have an option to do something on a long press for whatever reason. So I tried to use .gesture(LongPressGesture() ... ). This approach works and shows the tap indication. Unfortunately, that doesn't work with a ScrollView: you can't scroll it anymore!
So I did some research and I found out that there has to be a TapGesture before the LongPressGesture so ScrollView works properly. That's the case indeed but then my LongPressGesture doesn't work anymore.
Hope somebody has a solution...
struct ContentView: View {
var body: some View {
ScrollView(.horizontal){
HStack{
ForEach(0..<5){ _ in
Button()
}
}
}
}
}
struct Button: View{
#GestureState var isDetectingLongPress = false
#State var completedLongPress = false
var body: some View{
Circle()
.foregroundColor(.red)
.frame(width: 100, height: 100)
.opacity(self.isDetectingLongPress ? 0 : 1)
// That works, but there is no indication for the user that the UI recognized the gesture
// .onTapGesture {
// print("Tapped!")
// }
// .onLongPressGesture(minimumDuration: 0.5){
// print("Long pressed!")
// }
// The approach (*) shows the press indication, but the ScrollView is stuck because there is no TapGesture
// If I add a dummy TapGesture, the LongPressGesture won't work anymore but now the ScrollView works as expected
//.onTapGesture {}
// (*)
.gesture(LongPressGesture()
.updating(self.$isDetectingLongPress) { currentstate, gestureState,
transaction in
gestureState = currentstate
}
.onEnded { finished in
self.completedLongPress = finished
}
)
}
}
I've tried many combinations of trying onTapGesture + LongPressGesture + custom timings and animations and many work /almost/ but leave minor annoyances. This is what I found that works perfectly. Tested on iOS 13.6.
With this solution your scroll view still scrolls, you get the button depression animation, long pressing on the button works too.
struct MainView: View {
...
Scrollview {
RowView().highPriorityGesture(TapGesture()
.onEnded { _ in
// The function you would expect to call in a button tap here.
})
}
}
struct RowView: View {
#State var longPress = false
var body: some View {
Button(action: {
if (self.longPress) {
self.longPress.toggle()
} else {
// Normal button code here
}
}) {
// Buttons LaF here
}
// RowView code here.
.simultaneousGesture(LongPressGesture(minimumDuration: 0.5)
.onEnded { _ in
self.longPress = true
})
}
}
I used the following example ( iOS SwiftUI: pop or dismiss view programmatically ) in my code, but I don't know how to create an animation just like flipping a page and put several seconds delay when [Button] tapped.Does anyone know a solution?
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: {
//withAnimation(.linear(duration: 5).delay(5))// Error occurred in dalay.(Type of expression is ambiguous without more context)
withAnimation(.linear(duration: 5)) // not work
{
self.presentationMode.wrappedValue.dismiss()
}
}
)
}
}
struct RootView: View {
var body: some View {
VStack {
NavigationLink(destination: DetailView())
{ Text("I am Root. Tap for Detail View.")
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
RootView()
}
}
}
This would be one way to do it. Without the NavigationLink you have full control over all animations and transitions.
struct DetailView: View {
#Binding var showDetail:Bool
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: {
withAnimation(Animation.linear.delay(2)){
self.showDetail = false
}
}
).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.yellow)
}
}
struct RootView: View {
#State var showDetail = false
var body: some View {
VStack {
if showDetail{
DetailView(showDetail:self.$showDetail).transition(.move(edge: .trailing))
}else{
Button("I am Root. Tap for Detail View."){
withAnimation(.linear){
self.showDetail = true
}
}
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.red)
}
}
You can dispatchQueue delay?
Button(
"Here is Detail View. Tap to go back.",
action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.presentationMode.wrappedValue.dismiss()
}})