This simple code snippet recognizes a single tap gesture, and a triple tap gesture, but not a double tap gesture. Any idea why?
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.onTapGesture(count: 3) {
print ("tap thrice 3")
}
.onTapGesture(count: 2) {
print ("tap twice 2")
}
.onTapGesture(count: 1) {
print ("tap once 1")
}
}
}
Related
There was a similar question Center Item Inside Horizontal Stack that I followed the Asperi's basic answer that suited for me ( other answer did not worked, too).
struct HeaderTestView: View {
#State private var currentDate = Date()
var body: some View {
ZStack {
HStack {
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.left.fill")
}
}
Spacer()
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.right.fill")
}
}
}
HStack {
Text("Title")
DatePicker("Date", selection: $currentDate, displayedComponents: [.date])
}
}
}
}
My Center Item are a Text and a DatePicker, everything works as expected until the date picker is placed. The title of DatePicker is placed on the left and date is placed on the right.
Without the DatePicker
With the DatePicker
Any idea to solve this beautifully so that a Text and a DatePicker are close on the center while there are two HStacks that are adjested to on the left and right
Add .fixedSize() to the HStack containing the Text view and DatePicker:
struct ContentView: View {
#State private var currentDate = Date()
var body: some View {
ZStack {
HStack {
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.left.fill")
}
}
Spacer()
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.right.fill")
}
}
}
HStack {
Text("Title")
DatePicker("Date", selection: $currentDate, displayedComponents: [.date])
}
.fixedSize() // here
}
}
}
or add .fixedSize() to the DatePicker.
I'm trying to change the brightness of a TabView in SwiftUI, but whenever the brightness toggles, the sub-views change their vertical position. This is highly mysterious to me, as it seems strange for brightness to have any influence at all on the position of views.
If you've been following my past few questions, you might recognize that I want to change the brightness because I want to dim the background view as a new view slides up (on the press of a button). That's why, in the sample code, we must include the Z-Stack.
Sample Code:
struct ContentView: View {
#State var press: Bool = Bool()
var body: some View {
ZStack {
TabView {
NavigationView {
Button(action: { press.toggle() }) {
Text("Toggle")
}
}
.tabItem {
Image(systemName: "square.stack").font(.title)
Text("View One")
}
NavigationView {
Button(action: { press.toggle() }) {
Text("Toggle")
}
}
.tabItem {
Image(systemName: "checkmark.square")
Text("View Two")
}
}
.brightness(press ? -0.1: 0)
}
}
}
The issue is with the multiple NavigationViews that are inside the TabView You really are supposed to have only 1 NavigationView at the root of the view hierarchy. Using multiple NavigationViews is discouraged and can lead to unexpected results, like this. Therefore, the inside of the body should be:
NavigationView {
ZStack {
TabView {
Button(action: { press.toggle() }) {
Text("Toggle")
}
.tabItem {
Image(systemName: "square.stack")
Text("View One")
}
Button(action: { press.toggle() }) {
Text("Toggle")
}
.tabItem {
Image(systemName: "checkmark.square")
Text("View Two")
}
}
.brightness(press ? -0.1: 0)
}
}
I have tried to use Buttons and Navigation Links from various examples when researched on this channel and on the net. The NavigationLink would be ok, except that the NavigationView is pushing everything down in my view.
I have a view that contains an image and a text like this: ( x Close) but when I use the code below, the Close button is not doing anything.
In ContentView() I have a (?) button that takes me from WalkthroughView(), then to the PageTabView, then to this view, TabDetailsView:
ContentView():
ZStack {
NavigationView {
VStack {
Text("Hello World")
.padding()
.font(.title)
.background(Color.red)
.foregroundColor(.white)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
showOnBoarding = true
}
} label: {
Image(systemName: "questionmark.circle.fill")
}
}
}
}
.accentColor(.red)
.disabled(showOnBoarding)
.blur(radius: showOnBoarding ? 3.0 : 0)
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
}
.onAppear {
if !isWalkthroughViewShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
showOnBoarding.toggle()
isWalkthroughViewShowing = true
}
}
}
}
WalkthroughView():
var body: some View {
ZStack {
GradientView()
VStack {
PageTabView(selection: $selection)
// shows Previous/Next buttons only
ButtonsView(selection: $selection)
}
}
.transition(.move(edge: .bottom))
}
PageTabView():
var body: some View {
TabView(selection: $selection) {
ForEach(tabs.indices, id: \.self) { index in
TabDetailsView(index: index)
}
}
.tabViewStyle(PageTabViewStyle())
}
below, is the TabDetailsView():
At the top of the view is this Close button, when pressed, should send me back to ContentView, but nothing is happening.
struct TabDetailsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let index: Int
then, inside the body:
VStack(alignment: .leading) {
Spacer()
VStack(alignment: .leading) {
// Button to close each walkthrough page...
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
.padding(.leading)
.font(.title2)
.accentColor(.orange)
Spacer()
VStack {
Spacer()
Image(tabs[index].image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 415)
.padding(.leading, 10)
Text(tabs[index].title)
.font(.title)
.bold()
Text(tabs[index].text)
.padding()
Spacer()
}
.foregroundColor(.white)
}
}
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
Inserting view like above is not a presentation in standard meaning, that's why provided code does not work.
As this view is shown via showOnBoarding it should be hidden also via showOnBoarding, thus the solution is to pass binding to this state into view where it will be toggled back.
Due to deep hierarchy the most appropriate way is to use custom environment value. For simplicity let's use ResetDefault from https://stackoverflow.com/a/61847419/12299030 (you can rename it in your code)
So required modifications:
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
.environment(\.resetDefault, $showOnBoarding)
}
and in child view
struct TabDetailsView: View {
#Environment(\.resetDefault) var showOnBoarding
// .. other code
Button(action: {
self.showOnBoarding.wrappedValue.toggle()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
I have a SwiftUI List on iOS14.3 (Xcode12.3) and inside it's cells I want to have a Menu popup up when the user is tapping on a button. Additionally these cells have an onTapGesture and the problem is that this gesture is also triggered when the menu button is pressed.
Example:
List {
HStack {
Text("Cell content")
Spacer()
// Triggers also onTapGesture of cell
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
If you tap on the ellipsis image, the menu opens but also "Cell tapped" will be printed to the console. This is a problem for me, because in my real world example I am collapsing the cell within the tap gesture and of course I don't want that to happen when the user presses the menu button.
I found out, that when I long press on the button the gesture won't be triggered. But this is not acceptable UX in my opinion, took me a while to find that out for my self.
I also noticed, when I replace the Menu with a regular Button, then the cells gesture is not triggered while pressing (only with BorderlessButtonStyle or PlainButtonStyle, otherwise he is not active at all). But I don't know how to open a Menu from it's action.
List {
HStack {
Text("Cell content")
Spacer()
// Does not trigger cells onTapGesture, but how to open Menu from action?
Button(action: { print("Button tapped") }) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.buttonStyle(BorderlessButtonStyle())
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
Alternatively, you can override Menu onTapGesture:
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell content")
Spacer()
Menu(content: {
Button(action: {}) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: {}) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: {}) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.onTapGesture {} // override here
}
.contentShape(Rectangle())
.onTapGesture {
print("Cell tapped")
}
}
}
}
Also, there's no need to use .background(Color(.systemBackground)) to tap on spacers. This is not really flexible - what if you want change background color?
You can use contentShape instead:
.contentShape(Rectangle())
I'm not sure if this is a bug or expected behavior, but instead of fighting with it I would recommend to avoid such overlapping (because even with success today it might stop working on different systems/updates).
Here is possible solution (tested with Xcode 12.1 / iOS 14.1)
List {
HStack {
// row content area
HStack {
Text("Cell content")
Spacer()
}
.background(Color(.systemBackground)) // for tap on spacer area
.onTapGesture {
print("Cell tapped")
}
// menu area
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
}
I consider whether there is possibility to add more Views to TabView in SwiftUI then there is place for TabItems.
I have done something like this:
TabView(selection: $selectedTab) {
Text("Hello World 1")
.tabItem {
Image(systemName: "1.circle")
Text("Item 1")
}.tag(0)
Text("Hello World 2")
.tabItem {
Image(systemName: "2.circle")
Text("Item 2")
}.tag(1)
Text("Hello World 3")
.tabItem {
Image(systemName: "3.circle")
Text("Item 3")
}.tag(2)
Text("Hello World 4")
.tabItem {
Image(systemName: "4.circle")
Text("Item 4")
}.tag(3)
Text("Hello World 5")
.tabItem {
Image(systemName: "5.circle")
Text("")
}.tag(4)
Text("Hello World 5")
.tabItem {
Image(systemName: "6.circle")
Text("")
}.tag(5)
}
And there is More 3-dots button displayed automatically. But I would like to not show this additional tab items in tab bar just first 4 or 5 items and other items will be only programatically navigated. I would like to do it this way to add then Hamburger Menu with buttons that will be switching this other views.
I know that Hamburger/Navigation Drawer/Side Menu is not what apples recommends but such design will fit my application requirements great. :)
I hope the following approach would be useful. The idea is to have dynamic range which shows tab items depending on currently selected one.
For this demo selection, and including visible tabs, are changed depending on preview/next button, but it is not important - the of selection might be different, it just need update range of visible tabs depending on selected tab. That is it.
Here is how the demo behaves:
struct ContentView: View {
static let maxTabs = 8
#State var selectedTab = 2
#State var visibleTabs = [0, 1, 2, 3]
var body: some View {
VStack {
self.selectorView
Divider()
TabView(selection: $selectedTab) {
ForEach(visibleTabs, id: \.self) { i in
self.viewForTab(i)
.tabItem {
Image(systemName: "\(i).circle")
Text("Item \(i)")
}.tag(i)
}
}
}
}
var selectorView: some View {
HStack {
Button(action: {
let prev = self.selectedTab - 1
if prev >= 0 {
if prev < self.visibleTabs.min()! {
self.visibleTabs = self.visibleTabs.map { $0 - 1 }
}
self.selectedTab = prev
}
}) {
Text("< Prev").padding([.top, .horizontal])
}.disabled(self.selectedTab == 0)
Button(action: {
let next = self.selectedTab + 1
if next < Self.maxTabs {
if next > self.visibleTabs.max()! {
self.visibleTabs = self.visibleTabs.map { $0 + 1 }
}
self.selectedTab = next
}
}) {
Text("Next >").padding([.top, .horizontal])
}.disabled(self.selectedTab == Self.maxTabs - 1)
}
}
private func viewForTab(_ tag: Int) -> some View {
// Provide your view for requested tab tag
Text("Hello World \(tag)")
}
}