Bind to a struct property in a SwiftUI Mac app - swift

I'm building a macOS unit converter app in SwiftUI. In the example below, I have an acceleration view that has many text fields. These text fields represent different units of acceleration.
// AccelerationView.swift
struct AccelerationView: View {
#State private var accel = Acceleration()
#EnvironmentObject var formatter: Formatter
var body: some View {
VStack(alignment: .leading) {
Text("Metric").font(.subheadline)
HStack {
TextField("kilometer per second squared", value: $accel.kilometerPerSecondSquare, formatter: formatter.numberFormatter).frame(width: 120)
Text("km/s²").frame(width: 60, alignment: .leading)
}
HStack {
TextField("meter per second squared", value: $accel.meterPerSecondSquare, formatter: formatter.numberFormatter).frame(width: 120)
Text("m/s²").frame(width: 60, alignment: .leading)
}
HStack {
TextField("millimeter per second squared", value: $accel.millimeterPerSecondSquare, formatter: formatter.numberFormatter).frame(width: 120)
Text("mm/s²").frame(width: 60, alignment: .leading)
}
// and so on ...
}
.padding()
}
}
I would like to place the HStack into its own file to clean up the code in the view. My attempt at this is shown below:
// UnitTextField.swift
struct UnitTextField: View {
#State var value: Double = 0.0
let placeHolder: String
let label: String
var fieldWidth: CGFloat = 120
var labelWidth: CGFloat = 60
#EnvironmentObject private var formatter: Formatter
var body: some View {
HStack {
TextField(placeHolder, value: $value, formatter: formatter.numberFormatter)
.frame(width: fieldWidth)
.multilineTextAlignment(.trailing)
Text(label)
.frame(width: labelWidth, alignment: .leading)
}
}
}
This does not work because the UnitTextField value does not properly bind to the acceleration struct. I'm trying to accomplish something like this that I can use in the AccelerationView:
// AccelerationView.swift
struct AccelerationView: View {
#State private var accel = Acceleration()
#EnvironmentObject var formatter: Formatter
var body: some View {
VStack(alignment: .leading) {
Text("Metric").font(.subheadline)
UnitTextField(value: $accel.kilometerPerSecondSquare, placeHolder: "kilometer per second squared", label: "km/s²")
UnitTextField(value: $accel.meterPerSecondSquare, placeHolder: "meter per second squared", label: "m/s²")
UnitTextField(value: $accel.millimeterPerSecondSquare, placeHolder: "millimeter per second squared", label: "mm/s²")
// and so on ...
}
.padding()
}
}
Any suggestions on how to properly implement this in SwiftUI for a macOS application?

If to go from what you're going to accomplish (last snapshot), then I assume you need
struct UnitTextField: View {
#Binding var value: Double
instead of
struct UnitTextField: View {
#State var value: Double = 0.0

It could be like this with binding and environmentObject :
class MyFormat: Formatter, ObservableObject{
var numberFormat = NumberFormatter()
}
struct UnitTextField: View {
#Binding var value: Double
let placeHolder: String
let label: String
var fieldWidth: CGFloat = 120
var labelWidth: CGFloat = 60
#EnvironmentObject var formatter: MyFormat
var body: some View {
HStack {
TextField(placeHolder, value: $value, formatter: formatter.numberFormat)
.frame(width: fieldWidth)
.multilineTextAlignment(.trailing)
Text(label)
.frame(width: labelWidth, alignment: .leading)
}
}
}
struct Acceleration{
var kilometerPerSecondSquare : Double = 0.0
var meterPerSecondSquare : Double = 1.0
var millimeterPerSecondSquare : Double = 2.0
}
struct AccelerationView: View {
#State private var accel = Acceleration()
#EnvironmentObject var formatter: MyFormat
var body: some View {
VStack(alignment: .leading) {
Text("Metric").font(.subheadline)
UnitTextField(value: $accel.kilometerPerSecondSquare, placeHolder: "kilometer per second squared", label: "km/s²").environmentObject(formatter)
UnitTextField(value: $accel.meterPerSecondSquare, placeHolder: "meter per second squared", label: "m/s²").environmentObject(formatter)
UnitTextField(value: $accel.millimeterPerSecondSquare, placeHolder: "millimeter per second squared", label: "mm/s²").environmentObject(formatter)
// and so on ...
}
.padding()
}
}

Related

How to remove special characters from the `TextField` in SwiftUI

I need to apply some validations in my form like remove special character, accept only number, alphabets, alphanumeric, and only specific length of a string.
I have many text fields, in many places in my app. So that I'm creating extensions to Binding, and trying to apply conditions when editing.
These filters/conditions using for #State, #Published, and #Binding variables.
Here is my code:
struct ContentView: View {
#State var name = ""
var body: some View {
InputField(text: $name)
}
}
struct InputField: View {
#Binding var text: String
var body: some View {
VStack {
TextField("Name here...", text: $text.limit(6).alphaNumeric)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
}.padding()
}
}
extension Binding where Value == String {
var alphaNumeric: Binding<String> {
Binding<String>(get: { self.wrappedValue },
set: {
self.wrappedValue = $0.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)})
}
func limit(_ length: Int) -> Binding<String> {
Binding<String>(get: { self.wrappedValue },
set: {
print($0.prefix(length))
self.wrappedValue = String($0.prefix(length))
})
}
}
Here in the above code $text.limit(6).alphaNumeric, I'm trying to limit the length to 6 characters and only allow the alphaNumeric string.
How about a custom TextField with checks, instead of messing with Binding Wrapper itself:
(with OPs full code to proof that binding works)
struct ContentView: View {
#State var name = ""
#State var number = ""
var body: some View {
InputField(text: $name, number: $number)
}
}
struct InputField: View {
#Binding var text: String
#Binding var number: String
var body: some View {
VStack {
TextFieldWithCheck("Name here...", text: $text, limit: 15, allowed: .alphanumerics)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
TextFieldWithCheck("Phone no here...", text: $number, limit: 9, allowed: .decimalDigits)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
}.padding()
}
}
// here is the custom TextField itself
struct TextFieldWithCheck: View {
let label: LocalizedStringKey
#Binding var text: String
let limit: Int
let allowed: CharacterSet
init(_ label: LocalizedStringKey, text: Binding<String>, limit: Int = Int.max, allowed: CharacterSet = .alphanumerics) {
self.label = label
self._text = Binding(projectedValue: text)
self.limit = limit
self.allowed = allowed
}
var body: some View {
TextField(label, text: $text)
.onChange(of: text) { _ in
// all credits to Leo Dabus:
text = String(text.prefix(limit).unicodeScalars.filter(allowed.contains))
}
}
}
You can use .onChange(of: ). Should also work for #Binding etc.
#State private var inputText = ""
let maxCount = 20
var body: some View {
TextField("Input", text: $inputText)
.padding()
.background(.gray.opacity(0.1))
.padding()
.onChange(of: inputText) { _ in
// check character set
var checked = inputText.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
// check length
checked = String(checked.prefix(maxCount)) // regards to Leo Dabus
inputText = checked
}
}

SwiftUI - Button inside a custom Collection view is not tappable completely

I have a set of buttons to show for the user and I used CollectionView to align the buttons. Each button is a Vstack with an Image and Text components. The tap is reactive only on the image but not on Text and the padding space around.
I am looking to solve this to make it reactive all over the button.
I found suggestions
to set ContentShape to rectangle and it didn't work
use Hstack to insert spaces on Left and right of the Text but that didn't work either.
Sample code:
ToolBarItem:
var body: some View {
VStack {
Button(action: {
// Delegate event to caller/parent view
self.onClickAction(self.toolBarItem)
}) {
VStack {
HStack {
Spacer()
Image(self.toolBarItem.selectedBackgroundImage)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.padding(EdgeInsets(top: 5, leading: 3, bottom: 0, trailing: 3))
.frame(width: CGFloat(self.toolBarItem.cellWidth * 0.60),
height: CGFloat(self.toolBarItem.cellHeight * 0.60))
Spacer()
}
.contentShape(Rectangle())
HStack {
Spacer()
Text(self.toolBarMenuInfo.specialSelectedName)
.foregroundColor(Color.red)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 5, trailing: 0))
Spacer()
}
.contentShape(Rectangle())
}
.frame(width: CGFloat(self.toolBarItem.cellWidth),
height: CGFloat(self.toolBarItem.cellHeight))
.background(Color.blue.opacity(0.5))
}
}
}
The above ToolBarItem is placed inside the Collection view (custom Object created by me) for as many items required. Refer attachment and the tap occurs only on the image surrounded by green marking.
has anyone had similar issue? Any inputs is appreciated.
I strongly suspect that you issue has to do with the border. but I don't know exactly because you haven't provided that code.
Here is a version of the button view that would give you the effect you see to want.
struct FloatingToolbarButtonView: View {
#Binding var model: ButtonModel
let size: CGSize
var body: some View {
Button(action: {
//Set the model's variable to selected
model.isSelected.toggle()
//Perform the action
model.onClick()
}, label: {
VStack {
//REMOVE systemName: in your code
Image(systemName: model.imageName)
//.renderingMode(.original)
.resizable()
//Maintains proportions
.scaledToFit()
//Set Image color
.foregroundColor(.white)
//Works with most images to change color
.colorMultiply(model.colorSettings.imageNormal)
.padding(5)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
//Set border color/width
.border(Color.green, width: model.isSelected ? 3:0)
Spacer()
Text(model.label)
//Set Text color
.foregroundColor(model.colorSettings.labelNormal)
}
.padding(EdgeInsets(top: 5, leading: 3, bottom: 0, trailing: 3))
}).frame(width: size.width, height: size.height, alignment: .center)
.background(model.colorSettings.backgroundNormal)
}
}
And this is what the model I used looks like
//Holds Button information
struct ButtonModel: Identifiable{
let id: UUID = UUID()
var label: String
var imageName: String
///Action to be called when the button is pressed
var onClick: () -> Void
///identify if the user has selected this button
var isSelected: Bool = false
var colorSettings: ButtonColorSettings
}
I created the buttons in a view model so I can have an easy to to set the action and access isSelected as needed.
//ViewModel that deals with all the button creation and onClick actions
class FloatingToolbarParentViewModel: ObservableObject{
//Settings the buttons at this level lets you read `isPressed`
#Published var horizontalButtons: [ButtonModel] = []
#Published var moreButton: [ButtonModel] = []
#Published var verticalButtons: [ButtonModel] = []
init(){
horizontalButtons = horizontalSamples
moreButton = [mer]
verticalButtons = veticalSamples
}
}
//MARK: Buttons
extension FloatingToolbarParentViewModel{
//MARK: SAMPLES fill in with your data
var identify:ButtonModel {ButtonModel(label: "Identify", imageName: "arrow.up.backward", onClick: {print(#function + " Identfy")}, colorSettings: .white)}
var tiltak:ButtonModel {ButtonModel(label: "Tiltak", imageName: "scissors", onClick: {print(#function + " Tiltak")}, colorSettings: .white)}
var tegn:ButtonModel { ButtonModel(label: "Tegn", imageName: "pencil", onClick: {print(#function + " Tegn")}, colorSettings: .white)}
var bestand:ButtonModel {ButtonModel(label: "Bestand", imageName: "leaf", onClick: {print(#function + " Identfy")}, colorSettings: .red)}
var mer:ButtonModel {ButtonModel(label: "Mer", imageName: "ellipsis.circle", onClick: {print(#function + " Mer")}, colorSettings: .red)}
var kart:ButtonModel {ButtonModel(label: "Kart", imageName: "map.fill", onClick: {print(#function + " Kart")}, colorSettings: .white)}
var posisjon:ButtonModel {ButtonModel(label: "Posisjon", imageName: "magnifyingglass", onClick: {print(#function + " Posisjon")}, colorSettings: .white)}
var spor:ButtonModel {ButtonModel(label: "Spor", imageName: "circle.fill", onClick: {print(#function + " Spor")}, colorSettings: .red)}
var horizontalSamples :[ButtonModel] {[identify,tiltak,tegn,bestand]}
var veticalSamples :[ButtonModel] {[kart,posisjon,spor]}
}
The rest of the code to get the sample going is below. It isn't really needed but it will give you a working product
struct FloatingToolbarParentView: View {
#State var region: MKCoordinateRegion = .init()
#StateObject var vm: FloatingToolbarParentViewModel = .init()
var body: some View {
ZStack{
Map(coordinateRegion: $region)
ToolbarOverlayView( horizontalButtons: $vm.horizontalButtons, cornerButton: $vm.moreButton, verticalButtons: $vm.verticalButtons)
}
}
}
struct ToolbarOverlayView: View{
#State var buttonSize: CGSize = .zero
#Binding var horizontalButtons: [ButtonModel]
#Binding var cornerButton: [ButtonModel]
#Binding var verticalButtons: [ButtonModel]
var body: some View{
GeometryReader{ geo in
VStack{
HStack{
Spacer()
VStack{
Spacer()
FloatingToolbarView(buttons: $verticalButtons, buttonSize: buttonSize, direction: .vertical)
}
}
Spacer()
HStack{
Spacer()
FloatingToolbarView(buttons: $horizontalButtons, buttonSize: buttonSize)
FloatingToolbarView(buttons: $cornerButton, buttonSize: buttonSize)
}
//Adjust the button size on appear and when the orientation changes
.onAppear(perform: {
setButtonSize(size: geo.size)
})
.onChange(of: geo.size.width, perform: { new in
setButtonSize(size: geo.size)
})
}
}
}
//Sets the button size using and minimum and maximum values accordingly
//landscape and portrait have oppositive min and max
func setButtonSize(size: CGSize){
buttonSize = CGSize(width: min(size.width, size.height) * 0.15, height: max(size.width, size.height) * 0.1)
}
}
//Toolbar group for an array of butons
struct FloatingToolbarView: View {
#Binding var buttons :[ButtonModel]
let buttonSize: CGSize
var direction: Direction = .horizontal
var body: some View {
Group{
switch direction {
case .horizontal:
HStack(spacing: 0){
ForEach($buttons){$button in
FloatingToolbarButtonView(model: $button, size: buttonSize)
}
}
case .vertical:
VStack(spacing: 0){
ForEach($buttons){$button in
FloatingToolbarButtonView(model: $button, size: buttonSize)
}
}
}
}
}
enum Direction{
case horizontal
case vertical
}
}
#available(iOS 15.0, *)
struct FloatingToolbarParentView_Previews: PreviewProvider {
static var previews: some View {
FloatingToolbarParentView()
FloatingToolbarParentView().previewInterfaceOrientation(.landscapeLeft)
}
}
//Holds Button Color information
//You havent provided much info on this so I assume that you are setting the colors somewhere
struct ButtonColorSettings{
var labelNormal: Color
var imageNormal: Color
var backgroundNormal: Color
//Sample Color configuration per image
static var white = ButtonColorSettings(labelNormal: .white, imageNormal: .white, backgroundNormal: .black.opacity(0.5))
static var red = ButtonColorSettings(labelNormal: .black, imageNormal: .red, backgroundNormal: .white)
}
Have you tried putting .contentShape(Rectangle()) on the whole VStack inside the Button or on the button itself? That should probably solve it.

How to make an individual button icon change for each ForEach item on SwiftUI?

I am trying to make each individual item button change from a cart icon a different icon when clicked in my ForEach. But when I click on a button every button icon change. How can I fix this?
Thank you so much
#StateObject var vm = ShopViewModel()
#State var isShowing = false
#State var cartItemCount = 0
#State var itemCart = false
var body: some View {
NavigationView {
ScrollView(/*#START_MENU_TOKEN#*/.vertical/*#END_MENU_TOKEN#*/, showsIndicators: false) {
VStack {
ForEach(vm.foods) { food in
HStack {
NavigationLink(
destination: DetailView(foody: food, isCanceled: $isShowing),
label: {
Image(food.imageURL)
.resizable()
.frame(width: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, height: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/)
Text(food.name)
.bold()
Spacer()
Text("$\(food.price)").padding()
}).foregroundColor(.black)
Spacer()
Button(action: {
cartItemCount += 1
}, label: {
Image(systemName: "cart")
.frame(width: 70, height: 50)
.background(Color("yColor"))
.cornerRadius(30)
}).padding(.trailing, 30)
}.padding(.leading, 20)
}
}.padding(.top, 30)
}
.navigationTitle("Falco Shop")
.navigationBarItems(trailing: CartIcon(cartItemCount: $cartItemCount).padding(.top, 90).padding())
}
}
Here is an example how you can do this:
import SwiftUI
struct ContentView: View {
#StateObject var vm = ShopViewModel()
#State var isShowing = false
#State var cartItemCount = 0
#State var itemCart = false
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack {
ForEach(vm.foods.indices, id:\.self) { index in
HStack {
Text(vm.foods[index].name)
Spacer()
Button(action: {
cartItemCount += 1
vm.foods[index].selected.toggle()
}, label: {
Image(systemName: vm.foods[index].selected ? "cart.fill" : "cart")
.frame(width: 70, height: 50)
.background(Color("yColor"))
.cornerRadius(30)
}).padding(.trailing, 30)
}.padding(.leading, 20)
}
}.padding(.top, 30)
}
.navigationTitle("Falco Shop")
}
}
}
class ShopViewModel: ObservableObject {
#Published var foods: [Food] = [Food(name: "apple", price: 2.0, selected: false, imageURL: "apple")]
}
struct Food: Identifiable {
let id: UUID = UUID()
var name: String
var price: Double
var selected: Bool
var imageURL: String
}

Why My second view cannot jump back to the root view properly

My App currently has two pages, first page has a circle plus button which could lead us to a second page. Basically, I have a save button which after clicking it, we could get back to the rood page. I followed this link for going back to root view. I tried the most up voted code, his code works perfectly. I reduced his code to two scene (basically the same scenario as mine), which also works perfectly. But then I don't know why my own code, pasted below, doesn't work. Basically my way of handling going back to root view is the same as the one in the link.
//
// ContentView.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/22/20.
//
import SwiftUI
import UIKit
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif
struct ContentView: View {
#EnvironmentObject private var fridge : Fridge
private var dbStartWith=0;
#State var pushed: Bool = false
#State private var selection = 1;
#State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}.environmentObject(fridge)
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
#Binding var pushed : Bool
#EnvironmentObject private var fridgeView : Fridge
#State private var name = ""
#State private var count : Int = 1
#State private var category : String = "肉类";
#State var showCategory = false
#State var showCount = false
var someNumberProxy: Binding<String> {
Binding<String>(
get: { String(format: "%d", Int(self.count)) },
set: {
if let value = NumberFormatter().number(from: $0) {
self.count = value.intValue;
}
}
)
}
var body: some View{
ZStack{
NavigationView{
VStack{
Button (action: {
self.pushed = false ;
//let tempFood=Food(id: fridgeView.index!,name: name, count: count, category: category);
//fridgeView.addFood(food: tempFood);
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you read my code carefully, there are some variables are missing referencing. That's because I pasted part of the code that relates to my issue.
Food Class
//
// Food.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/23/20.
//
import Foundation
class Food: Identifiable {
init(id:Int, name: String, count: Int, category: String){
self.id=id;
self.name=name;
self.count=count;
self.category=category;
}
var id: Int
var name: String
var count: Int
var category: String
}
Fridge class
//
// Fridge.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/27/20.
//
import Foundation
class Fridge: ObservableObject{
init(){
db=DBhelper();
let result = setIndex(database: db!);
self.index = result.1;
self.container=result.0;
}
var db:DBhelper?
var index : Int?
#Published var container : [Food]?;
func setIndex(database: DBhelper) -> ([Food],Int){
let foodList : [Food] = database.read();
var index=0;
for food in foodList{
index = max(food.id,index);
}
return (foodList,(index+1));
}
func updateindex(index: inout Int){
index=index+1;
}
func testExist(){
if let data = db {
print("hi")
}
else{
print("doesnt exist")
}
}
func addFood(food:Food){
self.db!.insert(id: self.index!, name: food.name, count:food.count, category: food.category);
self.container!.append(food);
}
}
Because you implemented a new NaviagtionView in AddFoodView. Simply remove this and it should work. Look at the link you provided. There is no NavigationView in the child.
Correct me if Im wrong but the core code parts here that produce this issue are as follows:
Here you start:
struct ContentView: View {
#State var pushed: Bool = false
// Deleted other vars
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
// Here you navigate to the child view
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
Here you land and want to go back to root:
struct AddFoodView: View{
#Binding var pushed : Bool
// Deleted the other vars for better view
var body: some View{
ZStack{
NavigationView{ // <-- remove this
VStack{
Button (action: {
// here you'd like to go back
self.pushed = false;
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
For the future:
I have the feeling you might have troubles with the navigation in general.
Actually it is really simple:
You implement one NavigationView at the "root" / start of your navigation.
From there on you only use NavigationLinks to go further down to child pages. No NavigationView needed anymore.

How to put View on top of all other views in SwiftUI

I'm developing SwiftUI test app and I added my custom DropDown menu here.
All works fine except dropdown menu is below other views. So users can't see & click dropdown menu correctly.
Here's my dropdown menu.
import SwiftUI
var dropdownCornerRadius:CGFloat = 3.0
struct DropdownOption: Hashable {
public static func == (lhs: DropdownOption, rhs: DropdownOption) -> Bool {
return lhs.key == rhs.key
}
var key: String
var val: String
}
struct DropdownOptionElement: View {
var dropdownWidth:CGFloat = 150
var val: String
var key: String
#Binding var selectedKey: String
#Binding var shouldShowDropdown: Bool
#Binding var displayText: String
var body: some View {
Button(action: {
self.shouldShowDropdown = false
self.displayText = self.val
self.selectedKey = self.key
}) {
VStack {
Text(self.val)
Divider()
}
}.frame(width: dropdownWidth, height: 30)
.padding(.top, 15).background(Color.gray)
}
}
struct Dropdown: View {
var dropdownWidth:CGFloat = 150
var options: [DropdownOption]
#Binding var selectedKey: String
#Binding var shouldShowDropdown: Bool
#Binding var displayText: String
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(self.options, id: \.self) { option in
DropdownOptionElement(dropdownWidth: self.dropdownWidth, val: option.val, key: option.key, selectedKey: self.$selectedKey, shouldShowDropdown: self.$shouldShowDropdown, displayText: self.$displayText)
}
}
.background(Color.white)
.cornerRadius(dropdownCornerRadius)
.overlay(
RoundedRectangle(cornerRadius: dropdownCornerRadius)
.stroke(Color.primary, lineWidth: 1)
)
}
}
struct DropdownButton: View {
var dropdownWidth:CGFloat = 300
#State var shouldShowDropdown = false
#State var displayText: String
#Binding var selectedKey: String
var options: [DropdownOption]
let buttonHeight: CGFloat = 30
var body: some View {
Button(action: {
self.shouldShowDropdown.toggle()
}) {
HStack {
Text(displayText)
Spacer()
Image(systemName: self.shouldShowDropdown ? "chevron.up" : "chevron.down")
}
}
.padding(.horizontal)
.cornerRadius(dropdownCornerRadius)
.frame(width: self.dropdownWidth, height: self.buttonHeight)
.overlay(
RoundedRectangle(cornerRadius: dropdownCornerRadius)
.stroke(Color.primary, lineWidth: 1)
)
.overlay(
VStack {
if self.shouldShowDropdown {
Spacer(minLength: buttonHeight)
Dropdown(dropdownWidth: dropdownWidth, options: self.options, selectedKey: self.$selectedKey, shouldShowDropdown: $shouldShowDropdown, displayText: $displayText)
}
}, alignment: .topLeading
)
.background(
RoundedRectangle(cornerRadius: dropdownCornerRadius).fill(Color.white)
)
}
}
struct DropdownButton_Previews: PreviewProvider {
static let options = [
DropdownOption(key: "week", val: "This week"), DropdownOption(key: "month", val: "This month"), DropdownOption(key: "year", val: "This year")
]
static var previews: some View {
Group {
VStack(alignment: .leading) {
DropdownButton(displayText: "This month", selectedKey: .constant("Test"), options: options)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.foregroundColor(Color.primary)
VStack(alignment: .leading) {
DropdownButton(shouldShowDropdown: true, displayText: "This month", selectedKey: .constant("Test"), options: options)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.foregroundColor(Color.primary)
}
}
}
Here's how it looks like.
Has anyone faced this issue before? And Could anyone please help me solve this issue?
Thanks.
Try to put everything in ZStack and use zIndex for DropdownButton to put it atop, like
ZStack {
// ... other content here
DropdownButton(displayText: "This month",
selectedKey: .constant("Test"),
options: options).zIndex(10)
// ... other content here