Trying to make a modal that's similar to the "Create Event" modal in Apple's Calendar app. I've got my modal successfully showing using the following code in a parent NavigationView:
.navigationBarItems(trailing:
Button(action: {
self.isModal = true
}) {
Image(systemName: "plus").sheet(isPresented: $isModal, content: {
EventCreate(showModal: self.$isModal)
})
}
)
The modal shows successfully, but I can't get a NavigationBar to show in the modal, which looks like this:
struct EventCreate: View {
#Binding var showModal: Bool
#State var event = Event(id: 0, title: "", description: "", location: "", start: Date(), end: Date(), cancelled: false, public_occurrence: false, created: "", last_updated: "", guests: -1)
var body: some View {
NavigationView {
Form{
Section {
TextField("Title", text: $event.title)
TextField("Location", text: $event.location)
}
Section {
DatePicker(selection: $event.start, label: { Text("Start") })
DatePicker(selection: $event.end, label: { Text("End") })
}
}
}
.navigationBarTitle(Text("Create Event"), displayMode: .inline)
.navigationBarItems(leading:
Button("Close") {
self.showModal = false
})
}
}
The app builds, and the Form is displayed, but the NavigationView doesn't:
How can I make this show? Or is there another view I should be using instead of NavigationView
You need to put navigationBarTitle and navigationBarItems modifier inside the NavigationView, not outside it. These modifiers must be placed on the view that you are embedding, in your case the Form
struct EventCreate: View {
#Binding var showModal: Bool
#State var event = Event(id: 0, title: "", description: "", location: "", start: Date(), end: Date(), cancelled: false, public_occurrence: false, created: "", last_updated: "", guests: -1)
var body: some View {
NavigationView {
Form{
Section {
TextField("Title", text: $event.title)
TextField("Location", text: $event.location)
}
Section {
DatePicker(selection: $event.start, label: { Text("Start") })
DatePicker(selection: $event.end, label: { Text("End") })
}
}
.navigationBarTitle(Text("Create Event"), displayMode: .inline)
.navigationBarItems(leading:
Button("Close") {
self.showModal = false
})
}
}
}
This article by HackingWithSwift shows the correct placement.
Related
I have this AddWorkoutView and I am trying to build some forms similar to what Apple did with "Add new contact" sheet form.
Right now I am trying to add a form more complex than a simple TextField (something similar to "add address" from Apple contacts but I am facing the following issues:
in the Exercises section when pressing on a new created entry (exercise), both Picker and delete Button are triggered at the same time and the Picker gets automatically closed as soon as it gets open and the selected entry is also deleted when going back to AddWorkoutView.
Does anyone have any idea on how Apple implemented this kind of complex form like in the screenshow below?
Thanks to RogerTheShrubber response here I managed to somehow implement at least the add button and to dynamically display all the content I previously added, but I don't know to bring together multiple TextFields/Pickers/any other stuff in the same form.
struct AddWorkoutView: View {
#EnvironmentObject var workoutManager: WorkoutManager
#EnvironmentObject var dateModel: DateModel
#Environment(\.presentationMode) var presentationMode
#State var workout: Workout = Workout()
#State var exercises: [Exercise] = [Exercise]()
func getBinding(forIndex index: Int) -> Binding<Exercise> {
return Binding<Exercise>(get: { workout.exercises[index] },
set: { workout.exercises[index] = $0 })
}
var body: some View {
NavigationView {
Form {
Section("Workout") {
TextField("Title", text: $workout.title)
TextField("Description", text: $workout.description)
}
Section("Exercises") {
ForEach(0..<workout.exercises.count, id: \.self) { index in
HStack {
Button(action: { workout.exercises.remove(at: index) }) {
Image(systemName: "minus.circle.fill")
.foregroundColor(.red)
.padding(.horizontal)
}
Divider()
VStack {
TextField("Title", text: $workout.exercises[index].title)
Divider()
Picker(selection: getBinding(forIndex: index).type, label: Text("Type")) {
ForEach(ExerciseType.allCases, id: \.self) { value in
Text(value.rawValue)
.tag(value)
}
}
}
}
}
Button {
workout.exercises.append(Exercise())
} label: {
HStack(spacing: 0) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.green)
.padding(.trailing)
Text("add exercise")
}
}
}
}
.navigationTitle("Create new Workout")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Cancel")
}
.accessibilityLabel("Cancel adding Workout")
}
ToolbarItem(placement: .confirmationAction) {
Button {
} label: {
Text("Done")
}
.accessibilityLabel("Confirm adding the new Workout")
}
}
}
}
}
I have a details screen and i want to display outside the list a Text() view and then another List, but when i adding the Text() below and outside the List i can't see the view
Part of my code:
var body: some View {
NavigationView {
List {
Button(action: {}, label: {
Text("Name: \(name)")
})
Button(action: {}, label: {
Text("Description: \(description)")
})
Button(action: {}, label: {
Text("Numerical Target: \(numTarget)")
})
Picker(selection: $numUnitIndex, label: Text("Numerical Unit: \(numUnit)")) {
ForEach(0 ..< units.count) {
Text(self.units[$0]).tag($0).foregroundColor(.blue)//.listRowInsets(EdgeInsets())
}
}.pickerStyle(MenuPickerStyle())
Button(action: {}, label: {
Text("Start date: \(startDate)")
})
Button(action: {}, label: {
Text("End date: \(endDate)")
})
Button(action: {}, label: {
Text("Category: \(category)")
})
Spacer()
}.onAppear() {
setPickerValue()
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Goal Details")
.toolbar {
Button("Add Step") {
print("Help tapped!")
}
}
Text("Steps for this goal").background(Color.black).font(.system(size: 20))
}
}
}
struct GoalDetailsView_Previews: PreviewProvider {
static var previews: some View {
GoalDetailsView(id: "", authorId: "", name: "", description: "", startDate: "", endDate: "", numTarget: "", numUnit: "", category: "")
}
}
When you're placing more that one item inside NavigationView you need to specify container(VStack/HStack/ZStack) explicitly, like this:
NavigationView {
VStack {
List {
// content
}
Text("Steps for this goal").font(.system(size: 20))
}
}
Generally speaking, screen will be split into sections for each NavigationView sibling up to a system specific limit. For iOS this limit is 1, which makes all other views disappear, on iPadOS and macOS the limit is higher
I have a details screen on my app and for some reason when I try to put a picker inside the list I'm getting an empty gray box, and I can see it only if I scroll inside this gray box. Can someone help me, please?
Note: I'm a beginner in iOS development :-)
Thanks
My code:
struct GoalDetailsView: View {
var id: String
var authorId: String
var name: String
var description: String
var startDate: String
var endDate: String
var numTarget: String
var numUnit: String
var category: String
#State var numUnitIndex = 0
var units = ["other", "Kg", "$$", "Km", "Hours", "Days", "Weeks", "%"]
var body: some View {
NavigationView {
List {
Button(action: {}, label: {
Text("Name: \(name)")
})
Button(action: {}, label: {
Text("Description: \(description)")
})
Button(action: {}, label: {
Text("Numerical Target: \(numTarget)")
})
Form {
Section {
Picker(selection: $numUnitIndex, label: Text("Numerical Unit")) {
ForEach(0 ..< units.count) {
Text(self.units[$0]).tag($0).foregroundColor(.blue)
}
}
}
}
Spacer()
}.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Goal Details")
}
}
}
struct GoalDetailsView_Previews: PreviewProvider {
static var previews: some View {
GoalDetailsView(id: "", authorId: "", name: "", description: "", startDate: "", endDate: "", numTarget: "", numUnit: "", category: "")
}
}
you could try this, or some variation of it:
Form {
Section {
Picker(selection: $numUnitIndex, label: Text("Numerical Unit")) {
ForEach(0 ..< units.count) {
Text(self.units[$0]).tag($0).foregroundColor(.blue)
}
}
.pickerStyle(.inline) // <-- here
}
}.scaledToFit() // <-- here
I want to make a side menu with .fullScreenCover, when a user is logged in and press MyGarage button. the .fullScreenCover will dismiss and the main view will navigate to MyGarage View. But if user is not logged in the .fullScreenCover will dismiss and a loginView with .fullScreenCover will appear. My problem is, the .fullScreenCover will not work if I put 2 same .fullScreenCover inside the main view. Is there any way to solve this? I'm sorry it's a little bit difficult for me to explain.
Here's the code
SideMenuView
struct SideMenuView: View {
#Environment(\.presentationMode) var presentationMode
#Binding var showMyGarage: Bool
#Binding var showSignIn: Bool
var user = 0 //If user is 1, it is logged in
var body: some View {
NavigationView{
VStack{
Button(action: {
presentationMode.wrappedValue.dismiss()
if user == 1 {
self.showMyGarage = true
}else{
self.showSignIn = true
}
}, label: {
Text("My Garage")
})
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading:
HStack(spacing: 20){
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("X")
})
Text("Main Menu")
}
)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
MainView
struct HomeView: View {
#State var showSideMenu = false
#State private var showMyGarage = false
#State var showSignIn = false
var body: some View {
VStack{
Text("Home")
NavigationLink(destination: MyGarageView(showMyGarage: $showMyGarage), isActive: $showMyGarage){
EmptyView()
}
}
.navigationBarItems(leading:
Button(action: {
self.showSideMenu.toggle()
}, label: {
Text("Menu")
})
)
.fullScreenCover(isPresented: $showSideMenu, content: {
SideMenuView(showMyGarage: $showMyGarage, showSignIn: $showSignIn)
})
.fullScreenCover(isPresented: $showSignIn, content: {
SignInView()
})
}
}
struct MyGarageView: View {
#Binding var showMyGarage: Bool
var body: some View {
Text("MyGarage")
}
}
struct SignInView: View {
var body: some View {
Text("Sign In")
}
}
Try to attach them to different views, like
var body: some View {
VStack{
Text("Home")
.fullScreenCover(isPresented: $showSideMenu, content: {
SideMenuView(showMyGarage: $showMyGarage, showSignIn: $showSignIn)
})
NavigationLink(destination: MyGarageView(showMyGarage: $showMyGarage), isActive: $showMyGarage){
EmptyView()
}
.fullScreenCover(isPresented: $showSignIn, content: {
SignInView()
})
}
.navigationBarItems(leading:
Button(action: {
self.showSideMenu.toggle()
}, label: {
Text("Menu")
})
)
}
In my navigation, I want to be able to go from ContentView -> ModelListView -> ModelEditView OR ModelAddView.
Got this working, my issue now being that when I hit the Back button from ModelAddView, the intermediate view is omitted and it pops back to ContentView; a behaviour that
ModelEditView does not have.
There's a reason for that I guess – how can I get back to ModelListView when dismissing ModelAddView?
Here's the code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List{
NavigationLink(
destination: ModelListView(),
label: {
Text("1. Model")
})
Text("2. Model")
Text("3. Model")
}
.padding()
.navigationTitle("Test App")
}
}
}
struct ModelListView: View {
#State var modelViewModel = ModelViewModel()
var body: some View {
List(modelViewModel.modelValues.indices) { index in
NavigationLink(
destination: ModelEditView(model: $modelViewModel.modelValues[index]),
label: {
Text(modelViewModel.modelValues[index].titel)
})
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing:
NavigationLink(
destination: ModelAddView(modelViewModel: $modelViewModel), label: {
Image(systemName: "plus")
})
)
}
}
struct ModelEditView: View {
#Binding var model: Model
var body: some View {
TextField("Titel", text: $model.titel)
}
}
struct ModelAddView: View {
#Binding var modelViewModel: ModelViewModel
#State var model = Model(id: UUID(), titel: "")
var body: some View {
TextField("Titel", text: $model.titel)
}
}
struct ModelViewModel {
var modelValues: [Model]
init() {
self.modelValues = [ //mock data
Model(id: UUID(), titel: "Foo"),
Model(id: UUID(), titel: "Bar"),
Model(id: UUID(), titel: "Buzz")
]
}
}
struct Model: Identifiable, Equatable {
let id: UUID
var titel: String
}
Currently placing a NavigationLink in the .navigationBarItems may cause some issues.
A possible solution is to move the NavigationLink to the view body and only toggle a variable in the navigation bar button:
struct ModelListView: View {
#State var modelViewModel = ModelViewModel()
#State var isAddLinkActive = false // add a `#State` variable
var body: some View {
List(modelViewModel.modelValues.indices) { index in
NavigationLink(
destination: ModelEditView(model: $modelViewModel.modelValues[index]),
label: {
Text(modelViewModel.modelValues[index].titel)
}
)
}
.background( // move the `NavigationLink` to the `body`
NavigationLink(destination: ModelAddView(modelViewModel: $modelViewModel), isActive: $isAddLinkActive) {
EmptyView()
}
.hidden()
)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing: trailingButton)
}
// use a Button to activate the `NavigationLink`
var trailingButton: some View {
Button(action: {
self.isAddLinkActive = true
}) {
Image(systemName: "plus")
}
}
}