SwiftUI no hide animation - swiftui

I have noticed that when I'm coloring the background I will not get animations when removing Views.
If I remove Color(.orange).edgesIgnoringSafeArea(.all) then hide animation will work, otherwise Modal will disappear abruptly. Any solutions?
struct ContentView: View {
#State var show = false
func toggle() {
withAnimation {
show = true
}
}
var body: some View {
ZStack {
Color(.orange).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Modal")
}
if show {
Modal(show: $show)
}
}
}
}
struct Modal: View {
#Binding var show: Bool
func toggle() {
withAnimation {
show = false
}
}
var body: some View {
ZStack {
Color(.systemGray4).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Close")
}
}
}
}

You need to make animatable container holding removed view (and this makes possible to keep animation in one place). Here is possible solution.
Tested with Xcode 12 / iOS 14
struct ContentView: View {
#State var show = false
func toggle() {
show = true // animation not requried
}
var body: some View {
ZStack {
Color(.orange).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Modal")
}
VStack { // << major changes
if show {
Modal(show: $show)
}
}.animation(.default) // << !!
}
}
}
struct Modal: View {
#Binding var show: Bool
func toggle() {
show = false // animation not requried
}
var body: some View {
ZStack {
Color(.systemGray4).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Close")
}
}
}
}

Related

In SwiftUI, iOS15, 2nd level NavigationLink, isActive is not working

in iOS15, it is not working:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
Dest1().navigationTitle("Dest1")
} label: {
Text("to Destination 1")
}
}
}
}
struct Dest1: View {
#State var dest2Active: Bool = false
var body: some View {
NavigationLink(
destination: Button {
dest2Active = false // not working!!
} label: {Text("dismiss")} .navigationTitle("Dest2"),
isActive: $dest2Active
) {Text("to Destination 2")}
}
}
The dismiss button in Dest2 is not working!
I remember that in iOS14, this code works well.
How to resolve this?
Adding .isDetailLink(false) to the top level NavigationLink seems to solve the issue. Note that this works on iPhone iOS -- for iPad, you will need to use a StackNavigationStyle as #workingdog suggests in their answer.
The documentation is not clear on why this works (in fact, it refers specifically to multi-column navigation), but it seems to solve a number of NavigationLink-related issues. See, for example: https://developer.apple.com/forums/thread/667460
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
Dest1()
.navigationTitle("Dest1")
} label: {
Text("to Destination 1")
}.isDetailLink(false)
}
}
}
struct Dest1: View {
#State var dest2Active: Bool = false
var body: some View {
NavigationLink(isActive: $dest2Active) {
Dest2(dest2Active: $dest2Active)
} label: {
Text("to Destination 2")
}
}
}
struct Dest2: View {
#Binding var dest2Active : Bool
var body: some View {
Button {
dest2Active = false
} label: {
Text("Dismiss")
}.navigationTitle("Dest2")
}
}
You need to add .navigationViewStyle(.stack) to make it work.
Here is the test code that works for me.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
Dest1().navigationTitle("Dest1")
} label: {
Text("to Destination 1")
}
}.navigationViewStyle(.stack) // <-- here the important bit
}
}
struct Dest1: View {
#State var dest2Active: Bool = false
var body: some View {
NavigationLink(
destination: Button {
dest2Active = false // now working!!
} label: {Text("dismiss")} .navigationTitle("Dest2"),
isActive: $dest2Active
) {Text("to Destination 2")}
}
}

SwiftUI Observed Object not updating

Really just starting out with SwiftUI and trying to get my head around MVVM.
The code below displays a height and 4 toggle buttons, thus far I have only connected 2.
Major up and Major Down.
When clicked I see in the console that the value is altered as expected.
What I don't see is the the main display updating to reflect the change.
I have tried refactoring my code to include the view model into each Struct but still not seeing the change.
I think I have covered the basics but am stumped, I'm using a single file for now but plan to move the Model and ViewModel into separate files when I have a working mockup.
Thanks for looking.
import SwiftUI
/// This is our "ViewModel"
class setHeightViewModel: ObservableObject {
struct ImperialAndMetric {
var feet = 16
var inches = 3
var meters = 4
var CM = 95
var isMetric = true
}
// The model should be Private?
// ToDo: Fix the private issue.
#Published var model = ImperialAndMetric()
// Our getters for the model
var feet: Int { return model.feet }
var inches: Int { return model.inches }
var meters: Int { return model.meters }
var cm: Int { return model.CM }
var isMetric: Bool { return model.isMetric }
/// Depending upon the selected mode, move the major unit up by one.
func majorUp() {
if isMetric == true {
model.meters += 1
print("Meters is now: \(meters)")
} else {
model.feet += 1
print("Feet is now: \(feet)")
}
}
/// Depending upon the selected mode, move the major unit down by one.
func majorDown() {
if isMetric == true {
model.meters -= 1
print("Meters is now: \(meters)")
} else {
model.feet -= 1
print("Feet is now: \(feet)")
}
}
/// Toggle the state of the display mode.
func toggleMode() {
model.isMetric = !isMetric
}
}
// This is our View
struct ViewSetHeight: View {
// UI will watch for changes for setHeihtVM now.
#ObservedObject var setHeightVM = setHeightViewModel()
var body: some View {
NavigationView {
Form {
ModeArea(viewModel: setHeightVM)
SelectionUp(viewModel: setHeightVM)
// Show the correct height format
if self.setHeightVM.isMetric == true {
ValueRowMetric(viewModel: self.setHeightVM)
} else {
ValueRowImperial(viewModel: self.setHeightVM)
}
SelectionDown(viewModel: setHeightVM)
}.navigationTitle("Set the height")
}
}
}
struct ModeArea: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
if viewModel.isMetric == true {
SwitchImperial(viewModel: viewModel)
} else {
SwitchMetric(viewModel: viewModel)
}
}
}
}
struct SwitchImperial: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Button(action: {
print("Imperial Tapped")
}, label: {
Text("Imperial").onTapGesture {
viewModel.toggleMode()
}
})
Spacer()
Text("\(viewModel.feet)\'-\(viewModel.inches)\"").foregroundColor(.gray)
}
}
}
struct SwitchMetric: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Button(action: {
print("Metric Tapped")
}, label: {
Text("Metric").onTapGesture {
viewModel.toggleMode()
}
})
Spacer()
Text("\(viewModel.meters).\(viewModel.cm) m").foregroundColor(.gray)
}
}
}
struct SelectionUp: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
HStack {
Button(action: {
print("Major Up Tapped")
viewModel.majorUp()
}, label: {
Image(systemName: "chevron.up").padding()
})
Spacer()
Button(action: {
print("Minor Up Tapped")
}, label: {
Image(systemName: "chevron.up").padding()
})
}
}
}
}
struct ValueRowImperial: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Spacer()
Text(String(viewModel.feet)).accessibility(label: Text("Feet"))
Text("\'").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
Text("-").foregroundColor(Color.gray).padding(.horizontal, -10.0)
Text(String(viewModel.inches)).accessibility(label: Text("Inches"))
Text("\"").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
Spacer()
}.font(.largeTitle).padding(.zero)
}
}
struct ValueRowMetric: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Spacer()
Text(String(viewModel.meters)).accessibility(label: Text("Meter"))
Text(".").padding(.horizontal, -5.0).padding(.top, -15.0)
Text(String(viewModel.cm)).accessibility(label: Text("CM"))
Text("m").padding(.horizontal, -5.0).padding(.top, -15.0).font(.body)
Spacer()
}.font(.largeTitle)
}
}
struct SelectionDown: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
HStack {
Button(action: {
print("Major Down Tapped")
viewModel.majorDown()
}, label: {
Image(systemName: "chevron.down").padding()
})
Spacer()
Button(action: {
print("Minor Down Tapped")
}, label: {
Image(systemName: "chevron.down").padding()
})
}
}
}
}
At the moment you receive the setHeightViewModel in various views as a var,
you should receive it as an ObservedObject.
I suggest you try this, in ViewSetHeight
#StateObject var setHeightVM = setHeightViewModel()
and in all your other views where you pass this model, use:
#ObservedObject var viewModel: setHeightViewModel
such as in ModeArea, SwitchImperial, SwitchMetric, SelectionUp, etc...
Alternatively you could use #EnvironmentObject ... to pass the setHeightViewModel
to all views that needs it.

On tap gesture on an image SwiftUI

So I am trying to change between views when someone clicks on the image, but the action after the ontapgesture is always "expression is unused". I tried changing it to a navigation view as well as other things but I feel like nothing seems to be working.
Code below:
struct MenuView2: View {
#State private var menuu2 = menu2()
var body: some View {
ScrollView(){
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
menuu2
}
}
}
}
}
Thank you.
The error you get is "correct" in that menuu2 does not do anything, it is just there.
There are a number of ways to change view on tap, this is just one way:
struct MenuView2: View {
#State private var menuu2 = menuu2()
#State private var changeView = false
var body: some View {
changeView ? AnyView(theOtherView) : AnyView(theScrollView)
}
var theOtherView: some View {
// menuu2 presumably
// just for testing
Text("theOtherView").onTapGesture {
self.changeView = false
}
}
var theScrollView: some View {
ScrollView() {
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
self.changeView = true
}
}
}
}
}

Dismiss sheet SwiftUI

I'm trying to implement a dismiss button for my modal sheet as follows:
struct TestView: View {
#Environment(\.isPresented) var present
var body: some View {
Button("return") {
self.present?.value = false
}
}
}
struct DataTest : View {
#State var showModal: Bool = false
var modal: some View {
TestView()
}
var body: some View {
Button("Present") {
self.showModal = true
}.sheet(isPresented: $showModal) {
self.modal
}
}
}
But the return button when tapped does nothing. When the modal is displayed the following appears in the console:
[WindowServer] display_timer_callback: unexpected state (now:5fbd2efe5da4 < expected:5fbd2ff58e89)
If you force unwrap present you find that it is nil
How can I dismiss .sheet programmatically?
iOS 15+
Starting from iOS 15 we can use DismissAction that can be accessed as #Environment(\.dismiss).
There's no more need to use presentationMode.wrappedValue.dismiss().
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
Use presentationMode from the #Environment.
Beta 6
struct SomeView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Ohay!")
Button("Close") {
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
For me, beta 4 broke this method - using the Environment variable isPresented - of using a dismiss button. Here's what I do nowadays:
struct ContentView: View {
#State var showingModal = false
var body: some View {
Button(action: {
self.showingModal.toggle()
}) {
Text("Show Modal")
}
.sheet(
isPresented: $showingModal,
content: { ModalPopup(showingModal: self.$showingModal) }
)
}
}
And in your modal view:
struct ModalPopup : View {
#Binding var showingModal:Bool
var body: some View {
Button(action: {
self.showingModal = false
}) {
Text("Dismiss").frame(height: 60)
}
}
}
Apple recommend (in WWDC 2020 Data Essentials in SwiftUI) using #State and #Binding for this. They also place the isEditorPresented boolean and the sheet's data in the same EditorConfig struct that is declared using #State so it can be mutated, as follows:
import SwiftUI
struct Item: Identifiable {
let id = UUID()
let title: String
}
struct EditorConfig {
var isEditorPresented = false
var title = ""
var needsSave = false
mutating func present() {
isEditorPresented = true
title = ""
needsSave = false
}
mutating func dismiss(save: Bool = false) {
isEditorPresented = false
needsSave = save
}
}
struct ContentView: View {
#State var items = [Item]()
#State private var editorConfig = EditorConfig()
var body: some View {
NavigationView {
Form {
ForEach(items) { item in
Text(item.title)
}
}
.navigationTitle("Items")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: presentEditor) {
Label("Add Item", systemImage: "plus")
}
}
}
.sheet(isPresented: $editorConfig.isEditorPresented, onDismiss: {
if(editorConfig.needsSave) {
items.append(Item(title: editorConfig.title))
}
}) {
EditorView(editorConfig: $editorConfig)
}
}
}
func presentEditor() {
editorConfig.present()
}
}
struct EditorView: View {
#Binding var editorConfig: EditorConfig
var body: some View {
NavigationView {
Form {
TextField("Title", text:$editorConfig.title)
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(action: save) {
Text("Save")
}
.disabled(editorConfig.title.count == 0)
}
ToolbarItem(placement: .cancellationAction) {
Button(action: dismiss) {
Text("Dismiss")
}
}
}
}
}
func save() {
editorConfig.dismiss(save: true)
}
func dismiss() {
editorConfig.dismiss()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(items: [Item(title: "Banana"), Item(title: "Orange")])
}
}

How to go to another view with button click

I have a button in my code and I have a file called LogindView.swift
I cannot get the code to open another view file when clicking on the button.
Can anybody give me an example on how to do it.
In my button action I have tried to write LogindView() but i just gives me a warning.
"Result of 'LogindView' initializer is unused"
Button(action: {
// Do action
LogindView()
}, label: {
//** Label text
Text("Logind")
.font(.headline)
.padding(.all)
.foregroundColor(Color.white)
})
.background(Color.blue)
You essentially have 3 options to transition between views depending on your needs.
First, you can use a NavigationView. This will provide a back button and will allow the user to go back. Note that there are some bugs currently when you don't put the NavigationLink inside of a List as per https://stackoverflow.com/a/57122621/3179416
import SwiftUI
struct MasterView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: LoginView()) {
Text("Login")
}
}
.navigationBarTitle(Text("Master"))
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Second, you can present a modal using .sheet. This will present a modal that appears on top of the current view but it can be dismissed by the user by dragging it down.
import SwiftUI
struct MasterView: View {
#State var isModal: Bool = false
var body: some View {
Button("Login") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
LoginView()
})
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Third, you can just use an if statement to change the current view to your Login View like so
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
} else {
Button("Login") {
self.showLoginView = true
}
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
If you would like to animate this, so that the transition doesn't appear so abruptly, you can also do this:
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
.animation(.spring())
.transition(.slide)
} else {
Button("Login") {
withAnimation {
self.showLoginView = true
}
}.animation(.none)
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
You can use navigation link instead button
var body: some View {
VStack {
Text("Title")
.font(.headline)
Image("myimage").clipShape(Circle())
Text("mytext").font(.title)
NavigationLink(destination: AnotherView()) {
Image(systemName: "person.circle").imageScale(.large)
}
}
}