How should I code this `#ViewBuilder` function for conditional contextMenu? - swiftui

Because the code for compositing context menu is a little lengthy, I want to code it like this:
var body: some View {
Text("Hello World")
.contextMenu(changingStatusID == nil ? contextMenu() : nil)
}
#ViewBuilder
func contextMenu() -> ContextMenu { //<-- Problem goes here, I don't know how to set the parameters and it's return type
ContextMenu {
Button {
edit()
} label: {
Label("Edit", systemImage: "rectangle.and.pencil.and.ellipsis")
}
Divider()
if #available(macOS 12.0, *) {
Button(role: .destructive) {
do {
try delete()
}
catch {
showAlert = true
}
} label: {
Label("Delete", systemImage: "trash")
}
.alert("Failed to delete.", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
} else {
Button {
do {
try delete()
}
catch {
showAlert = true
}
} label: {
Label("Delete ⚠︎", systemImage: "trash")
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Failed"),
message: Text("Unable to delete this task."),
dismissButton: .default(Text("OK")))
}
}
}
}
XCode shows error in the line of function name func contextMenu():
Cannot convert value of type 'ContextMenu<TupleView<(Button<Label<Text, Image>>, Divider, _ConditionalContent<AnyView, some View>)>>' to specified type '<<error type>>'
Reference to generic type 'ContextMenu' requires arguments in <...> Insert '<<#MenuItems: View#>>'
Static method 'buildBlock' requires that 'ContextMenu<TupleView<(Button<Label<Text, Image>>, Divider, _ConditionalContent<AnyView, some View>)>>' conform to 'View'

Here is an approach that works:
struct ContentView: View {
#State private var showContext = true
#State private var showAlert = false
var body: some View {
VStack {
Toggle("show contextMenu", isOn: $showContext)
Text("Hello World")
.contextMenu {
if showContext {
myContextMenu()
}
}
}
.padding()
}
#ViewBuilder
func myContextMenu() -> some View {
// ContextMenu { // not needed
Button {
//edit()
} label: {
Label("Edit", systemImage: "rectangle.and.pencil.and.ellipsis")
}
Divider()
Button(role: .destructive) {
do {
//try delete()
}
catch {
showAlert = true
}
} label: {
Label("Delete", systemImage: "trash")
}
.alert("Failed to delete.", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
// }
}
}

Related

How to popToRoot with the new iOS 16 NavigationStack inside a TabView?

I would like to be able to popToRoot from my first tabItem (when im on page2 (from first tabItem) and tap twice on first tabItem when im on second tabItem) without having to use the NavigationViewKit package and only the new NavigationStack, is that possible?
My solution works fine but I would like to get rid of the NavigationViewKit package.
Any good advice\code example will be appreciated.
My code :
Home page
import SwiftUI
import NavigationViewKit
struct Home_V: View {
#State var tabSelected: Int = 0
#State private var tappedTwice: Bool = false
// https://designcode.io/swiftui-handbook-tabbar-to-root-view
var handler: Binding<Int> { Binding(
get: { tabSelected },
set: {
if $0 == tabSelected {
// print("tabSelected == \(tabSelected)")
tappedTwice = true
}
tabSelected = $0
}
)}
// https://github.com/fatbobman/NavigationViewKit
#Environment(\.navigationManager) var nvmanager
var body: some View {
TabView(selection: handler) {
NavigationStack {
Page1()
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 0 {
self.tappedTwice = false
nvmanager.wrappedValue.popToRoot(tag:"Page1", animated: true){}
}
})
}
.tabItem {
Label("_HomeTitle", systemImage: "house")
.environment(\.symbolVariants, tabSelected == 0 ? .fill : .none)
}
.tag(0)
.navigationViewStyle(StackNavigationViewStyle())
NavigationStack {
Page2()
}
.tabItem {
Label("_MessagesTitle", systemImage: "envelope")
.environment(\.symbolVariants, tabSelected == 1 ? .fill : .none)
}
.tag(1)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
Page1
import SwiftUI
import NavigationViewKit
struct Page1: View {
var body: some View {
VStack {
Text("Page 1")
NavigationLink {
Page2()
} label: {
Text("Go to Page 2")
}
}
.navigationViewManager(for: "Page1", afterBackDo: {print("Back to Page1")})
}
}
I got it!
Here is my test code :
import SwiftUI
class NavigationCoordinator: ObservableObject {
#Published var path = NavigationPath()
func popToRoot() {
path.removeLast(path.count)
}
}
struct Test_PopToRoot_NavigationStack: View {
#State private var tabSelected: Int = 0
#State private var tappedTwice: Bool = false
#StateObject var navigationCoordinator = NavigationCoordinator()
#StateObject var navigationCoordinator2 = NavigationCoordinator()
// https://designcode.io/swiftui-handbook-tabbar-to-root-view
var handler: Binding<Int> { Binding(
get: { tabSelected },
set: {
if $0 == tabSelected {
// print("tabSelected == \(tabSelected)")
tappedTwice = true
}
tabSelected = $0
}
)}
var body: some View {
TabView(selection: handler) {
NavigationStack(path: $navigationCoordinator.path) {
VStack {
NavigationLink(value: 1) {
Test_PopToRoot_Tabview1()
.foregroundColor(.black)
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 0 {
self.tappedTwice = false
print("Home tapped twice!!!")
navigationCoordinator.popToRoot()
}
})
}
}
}
.environmentObject(navigationCoordinator)
.tabItem {
Label("_HomeTitle", systemImage: "house")
.environment(\.symbolVariants, tabSelected == 0 ? .fill : .none)
}
.tag(0)
.navigationViewStyle(StackNavigationViewStyle())
NavigationStack(path: $navigationCoordinator2.path) {
VStack {
NavigationLink(value: 1) {
Test_PopToRoot_Tabview2()
.foregroundColor(.black)
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 1 {
self.tappedTwice = false
print("2nd Tab tapped twice!!!")
navigationCoordinator2.popToRoot()
}
})
}
}
}
.environmentObject(navigationCoordinator2)
.tabItem {
Label("_MessagesTitle", systemImage: "envelope")
.environment(\.symbolVariants, tabSelected == 1 ? .fill : .none)
}
.tag(1)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
struct Test_PopToRoot_Tabview1: View {
var body: some View {
VStack {
NavigationLink(value: 2) {
Text("Go To Page2")
.foregroundColor(.black)
}
}
.navigationDestination(for: Int.self) { i in
Test_PopToRoot_Page2()
}
.navigationTitle(Text("Tabview1"))
}
}

SwiftUI: two column NavigationView not loading details after rotation?

I have the following SwiftUI code:
struct ContentView: View {
var body: some View {
NavigationView {
Form {
Section {
NavigationLink {
DetailsView()
} label: {
Text("Show details")
}
}
}
Text("Select details")
}
}
}
struct DetailsView: View {
#State var showModal = false
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
VStack {
Spacer()
Button {
showModal.toggle()
} label: {
Text("Show modal")
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
}
}
}
.fullScreenCover(isPresented: $showModal) {
MyModalView {
showModal = false
}
}
}
}
struct MyModalView: View {
var someAction:()->()
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Button {
someAction()
} label: {
Text("some action")
.foregroundColor(.white)
}
}
}
}
I am experiencing the following bug, where tapping on "Show details" won't show the DetailsView anymore after rotation...
How can I fix this?
Navigation view is really problematical but will be improved with IOS 16. Here is my solution
struct ContentView: View {
#State var navigate = false
var body: some View {
NavigationView {
Form {
Section {
NavigationLink(destination: DetailsView(navigate: $navigate), isActive: $navigate){
Text("Show details")
}
}
}
Text("Select details")
}
.navigationViewStyle(.automatic)
}
}
struct DetailsView: View {
#Binding var navigate: Bool
#State var showModal = false
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
VStack {
Spacer()
Button {
showModal.toggle()
} label: {
Text("Show modal")
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
}
}
}
.fullScreenCover(isPresented: $showModal) {
MyModalView {
showModal = false
}
}
.onAppear(){
navigate = false
}
}}
struct MyModalView: View {
var someAction:()->()
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Button {
someAction()
} label: {
Text("some action")
.foregroundColor(.white)
}
}
}}

Unable to use custom alignment with non-sibling views with animation

Goal: to have the tapped person in the ScrollView/HStack align it's centre with the other views using the explicit custom alignment, CentreScreenAlignment
Problem: currently nothing is happening when I tap on a given person.
Code:
extension HorizontalAlignment {
private enum CentreScreenAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[HorizontalAlignment.center]
}
}
static let centreScreenAlignment = HorizontalAlignment(CentreScreenAlignment.self)
}
struct ContentView: View {
#State private var centrePt: Int = 0
var body: some View {
GeometryReader { g in
VStack(alignment: .centreScreenAlignment) {
ZStack {
HStack {
Button("Cancel") {
// action
}
.padding(.leading)
Spacer()
Button("Done") {
// action
}
.padding(.trailing)
}
Button {
// action
} label: {
Image(systemName: "person.fill.badge.plus")
.coordinateSpace(name: "Add button")
.foregroundColor(.blue)
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
}
}
EmptyView()
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(1..<10) { index in
Group {
if index == self.centrePt {
Button("Patient #\(index)") {
//
}.transition(AnyTransition.identity)
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
} else {
Button("Person #\(index)") {
withAnimation {
centrePt = index
print(centrePt)
}
}
.transition(AnyTransition.identity)
}
}
}
}
}.padding()
Text("Hello")
}
}
}
}
Thanks very much.

ForEach Loop in Swift for Buttons

I want to use a ForEach loop to simplify the following code:
.toolbar {
ToolbarItem() {
Button {
}
label: {
Image(systemName: "magnifyingglass")
}
}
ToolbarItem() {
Button {
}
label: {
Image(systemName: "plus")
}
}
}
But it's not working. My approach only creates the "magnifyingglass" button.
My approach:
let toolbar = ["magnifyingglass", "plus"]
.toolbar {
ToolbarItem() {
ForEach(toolbar.indices) { index in
Button {
}
label: {
Image(systemName: toolbar[index])
}
}
}
}
you could try this:
struct ContentView: View {
let toolbar = ["magnifyingglass", "plus"]
var body: some View {
NavigationView {
Text("testing")
.toolbar {
ToolbarItem() {
HStack { // <--- here
ForEach(toolbar.indices) { index in
Button { }
label: { Image(systemName: toolbar[index]) }
}
}
}
}
}
}
}
import SwiftUI
struct ContentView: View {
let toolbar = ["magnifyingglass", "plus"]
var body: some View {
NavigationView {
Text("Toolbar")
.navigationTitle("")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
ForEach(toolbar, id: \.self) { index in
Button {
} label: {
Image(systemName: ("\(index)"))
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I suggest (not tested) to put the for loop outside of ToolbarItem() :
let toolbar = ["magnifyingglass", "plus"]
.toolbar {
ForEach(toolbar.indices) { index in
ToolbarItem() {
Button {
}
label: {
Image(systemName: toolbar[index])
}
}
}
}

SwiftUI - Button Action to Alert and take action

I am trying to send alert based on a condition, but the Navigation link is executing regardless of the condition. I was hoping for an intercept.
Goal:
If condition is not me then do not launch new view
New View is launching and then alert.
I am sure my code is incorrect, but I am unsure how I should achieve this
Thanks in advance.
var body: some View {
NavigationView {
VStack {
Button(action: {}) {
//NavigationLink(destination: secondView()) {
NavigationLink(destination: checkState()) {
Text("Add to Cart")
}.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
}.padding()
} // End of the GetOrder Struct
struct GetdOrderView_Previews: PreviewProvider {
static var previews: some View {
GetdOrderView()
}
}
}
struct checkState: View {
#ObservedObject var calcCheck = MealOrder()
#State var showingAlert = false
#State var myToggle = false
var body: some View {
NavigationView {
VStack {
Button(action: {
//Enter Action here
if self.myToggle == true {
self.showingAlert = true
} else {
self.showingAlert = true
}
}) {
Text("This is a test")
}.padding()
.font(.system(size: 14))
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(6)
//Insert Alerts
.alert(isPresented: $showingAlert) {
if self.myToggle {
return Alert(title: Text("Showing Message"), message: Text("Cart is valid"), dismissButton: .default(Text("OK")))
} else {
return Alert(title: Text("Showing Alert"), message: Text("Cart Empty"), dismissButton: .default(Text("Cancel")))
}
}
}
}
}
}
struct secondView: View {
var body: some View {
VStack {
Text("This is the second test")
}
}
}
Try the following approach
#State var activateLink = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: checkState(), isActive: $activateLink) {
EmptyView()
}
Button(action: {
if _YOUR_CONDITION_HERE_ {
self.activateLink = true
}
}) {
Text("Add to Cart")
.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
.onAppear { self.activateLink = false }
}.padding()
}// End of the GetOrder Struct