How to switch between views using #State and fullScreenCover in SwiftUI - swift

I'm trying to figure out how to switch between views using #State and fullScreenCover in SwiftUI
I have a main view and 2 simple views (View1 and View2). View1 must open on the tap of the "Show view 1" button, and View2 when typing on the "Show view 2" button. I also created 2 #State variables for this to work and initialized it with a .View1 (ActiveFullscreenview enum):
#State private var showFullscreenView = false
#State private var activeFullscreenView: ActiveFullscreenView = .View1
The problem is that it constantly showing the View1 even when I tap on the Show view 2 Button.
I thought that when tapping on the button and giving the activeFullscreenView variable a value of .View2 , it should change the variable and show the fullScreenView.
I supposed that it was because I called the showFullscreenView and it changed the variable and after that the default value was .View1 so it didn't change it.
tried this too:
self.showFullscreenView = true
self.activeFullscreenView = .View2
The second assumption was that I shouldn't give this variable the initial value. But in this case it adds some additional initialisation code that is not convenient:
#State private var activeFullscreenView: ActiveFullscreenView = .View1
Currently I have the following code that has the described issue:
import SwiftUI
struct MainView: View {
enum ActiveFullscreenView {
case View1, View2
}
#State private var showFullscreenView = false
#State private var activeFullscreenView: ActiveFullscreenView = .View1
var body: some View {
ZStack {
VStack {
// Button to show View 1 in FullScreen
Button("Show view 1", action: {
self.activeFullscreenView = .View1
self.showFullscreenView = true
})
// Button to show View 2 in FullScreen
Button("Show view 2", action: {
self.activeFullscreenView = .View2
self.showFullscreenView = true
})
}
}
.fullScreenCover(isPresented: $showFullscreenView) {
switch self.activeFullscreenView {
case .View1:
Text("View1")
case .View2:
Text("View2")
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Any help related to solving the problem appreciated. Convenient ways ways about how to achieve the same results of showing separate views using fullscreenCover appreciated.
Working in Xcode 12.4, latest iOS 14

With the help of #jnpdx who provided
This Answer that is Similar to My problem the problem is solved. Here is the code. As long as fullScreenCover and sheet are both modals, I renamed enum to ModalView:
struct MainView: View {
// enum to decide which view to present:
enum ModalView: String, Identifiable { // Identifiable
case View1, View2
var id: String {
return self.rawValue
}
}
#State var activeModalView : ModalView? = nil
var body: some View {
ZStack {
VStack {
// Button to show View 1
Button("Show view 1", action: {
self.activeModalView = .View1
})
// Button to show View 2
Button("Show view 2", action: {
self.activeModalView = .View2
})
}
} // use fullScreenCover or sheet depending on your needs
.fullScreenCover(item: $activeModalView) { activeModalValue in
switch activeModalValue {
case .View1:
Text("View1")
case .View2:
Text("View2")
}
}
}
}

Related

Issue with SwiftUI Segmented Picker

I am relatively new to SwiftUI and I'm trying to work on my first app. I am trying to use a segmented picker to give the user an option of changing between a DayView and a Week View. In each on of those Views, there would be specific user data that whould be shown as a graph. The issue I am having is loading the data. I posted the code below, but from what I can see, the issue comes down to the following:
When the view loads in, it starts with loading the dayView, since the selectedTimeInterval = 0. Which is fine, but then when the users presses on the "Week" in the segmented Picker, the data does not display. This due to the rest of the View loading prior to the .onChange() function from the segmented picker running. Since the .onChange is what puts the call into the viewModel to load the new data, there is no data. You can see this in the print statements if you run the code below.
I would have thought that the view load order would have been
load segmented picker
run the .onChange if the value changed
load the rest of the view
but the order actual is
load segmented picker,
load the rest of the view (graph loads with no data here!!!!!)
run the .onChange if the value has changed.
I am pretty lost so any help would be great! Thank you so much!
import SwiftUI
import OrderedCollections
class ViewModel: ObservableObject{
#Published var testDictionary: OrderedDictionary<String, Int> = ["":0]
public func daySelected() {
testDictionary = ["Day View Data": 100]
}
public func weekSelected() {
testDictionary = ["Week View Data": 200]
}
}
struct ContentView: View {
#State private var selectedTimeInterval = 0
#StateObject private var vm = ViewModel()
var body: some View {
VStack {
Picker("Selected Date", selection: $selectedTimeInterval) {
Text("Day").tag(0)
Text("Week").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: selectedTimeInterval) { _ in
let _ = print("In on change")
//Logic to handle different presses of the selector
switch selectedTimeInterval {
case 0:
vm.daySelected()
case 1:
vm.weekSelected()
default:
print("Unknown Selected Case")
}
}
switch selectedTimeInterval {
case 0:
let _ = print("In view change")
Day_View()
case 1:
let _ = print("In view change")
Week_View(inputDictionary: vm.testDictionary)
default:
Text("Whoops")
}
}
}
}
struct Day_View: View {
var body: some View {
Text("Day View!")
}
}
struct Week_View: View {
#State private var inputDictionary: OrderedDictionary<String,Int>
init(inputDictionary: OrderedDictionary<String,Int>) {
self.inputDictionary = inputDictionary
}
var body: some View {
let keys = Array(inputDictionary.keys)
let values = Array(inputDictionary.values)
VStack {
Text(keys[0])
Text(String(values[0]))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In your WeekView, change
#State private var inputDictionary: OrderedDictionary<String,Int>
to
private let inputDictionary: OrderedDictionary<String,Int>
#State is for the local state of the view. The idea is that you are initing it with initial state and from then on the view itself will change it and cause re-renders. When the WeekView is re-rendered, SwiftUI is ignoring the parameter you pass into the init and copying it from the previous WeekView to maintain state.
But, you want to keep passing in the dictionary from ContentView and cause re-renders from the parent view.
The main issue is that the initialization of the #State property wrapper is wrong.
You must use this syntax
#State private var inputDictionary: OrderedDictionary<String,Int>
init(inputDictionary: OrderedDictionary<String,Int>) {
_inputDictionary = State(wrappedValue: inputDictionary)
}
Or – if inputDictionary is not going to be modified – just declare it as (non-private) constant and remove the init method
let inputDictionary: OrderedDictionary<String,Int>

Why does SwiftUI automatically disable all buttons when dismissing a sheet and then changing the NavigationPath?

This example consists of a simple NavigationStack leading to Subview. The latter displays SomeSheet which, when its button is tapped, dismisses itself and changes the NavigationPath in order to go back to ContentView.
The mechanism works perfectly. However, all the buttons in ContentView are automatically disabled (example in video). Why does this happen? Is it a SwiftUI bug?
enum Root: Hashable {
case subview
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
NavigationStack(path: $model.path) {
List {
Button("Some button") {} // FIXME: All ContentView buttons are disabled when SomeSheet's button is tapped
NavigationLink(value: Root.subview) {
Text("Go to Subview")
}
}
.navigationDestination(for: Root.self) { _ in Subview() }
}
.environmentObject(model)
}
}
extension ContentView {
#MainActor final class Model: ObservableObject {
#Published var path = NavigationPath()
}
}
struct Subview: View {
#State private var sheetIsPresented = false
var body: some View {
Button("Show sheet") { sheetIsPresented = true }
.sheet(isPresented: $sheetIsPresented) { SomeSheet() }
}
}
struct SomeSheet: View {
#EnvironmentObject var model: ContentView.Model
#Environment(\.dismiss) var dismiss
var body: some View {
Button("Dismiss and go back to ContentView") {
dismiss()
model.path.removeLast()
}
}
}
Tested with Xcode 14.0 (iOS 16).

Xcode 14 NavigationPath – two views pushed onto nav stack when multiple links in form section

Create new SwiftUI project
Replace ContentView with the code below
Tap either link.
Both views are pushed onto the nav stack
class ContentViewModel: ObservableObject {
#Published var navigationPath = NavigationPath()
}
struct ContentView: View {
#StateObject var viewModel = ContentViewModel()
var body: some View {
NavigationStack(path: $viewModel.navigationPath) {
Form {
Section {
VStack {
NavigationLink(value: 1) {
Text("Location")
}
NavigationLink(value: 2) {
Text("Category")
}
}
}
}.navigationDestination(for: Int.self) { route in
switch route {
case 1: Text("Location")
case 2: Text("Category")
default: Text("Unknown")
}
}.navigationTitle("Home")
}
}
}
What am I doing wrong? I would only expect the tapped item to be pushed into the stack.
Removing the VStack solves the problem

Issue with setting #State variable dynamically

As in the code below, the choosenKeyboardKnowledge is a #State variable and was initiated as the first object read from the cache. Then in the body, I iterate each object and wrap it into a Button so that when clicked it leads to the corresponding sheet view. But each time after I run the preview and click on whichever button in the list view it always shows the first default view (set in the initializer), and if I dismiss it and click on another line it shows the correct view.
struct KeyboardKnowledgeView: View {
var keyboardKnowledges: [KeyboardKnowledge]
#State private var choosenKeyboardKnowledge: KeyboardKnowledge
#State private var showSheet: Bool = false
init() {
keyboardKnowledges = KeyboardKnowledgeCache.getKeyboardKnowledges()
_choosenKeyboardKnowledge = State(initialValue: keyboardKnowledges[0])
}
var body: some View {
ZStack {
Color.bgGreen.ignoresSafeArea()
List(keyboardKnowledges) { knowledge in
Button(action: {
self.choosenKeyboardKnowledge = knowledge
self.showSheet.toggle()
}) {
Text(knowledge.name)
}
.sheet(isPresented: $showSheet) {
KeyboardKnowledgeDetailsView(keyboardKnowledge: choosenKeyboardKnowledge)
}
}
}
}
}

Why does modifying the label of a NavigationLink change which View is displayed in SwiftUI?

I have an #EnvironmentObject called word (of type Word) whose identifier property I'm using for the label of a NavigationLink in SwiftUI. For the DetailView that is linked to the NavigationLink, all I have put is this:
struct DetailView: View {
#EnvironmentObject var word: Word
var body: some View {
VStack {
Text(word.identifier)
Button(action: {
self.word.identifier += "a"
}) {
Text("Click to add an 'a' to Word's identifier")
}
}
}
}
The ContentView that leads to this DetailView looks like this (I've simplified my actual code to isolate the problem).
struct ContentView: View {
#EnvironmentObject var word: Word
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Text(word.identifier)
}
}
}
}
When I tap the button on the DetailView, I'd expect it to update the DetailView with a new word.identifier that has an extra "a" appended onto it. When I tap it, however, it takes me back to the ContentView, albeit with an updated word.identifier. I can't seem to find a way to stay on my DetailView when the word.identifier being used by the ContentView's NavigationLink is modified. Also, I am running Xcode 11.3.1 and am currently unable to update, so if this is has been patched, please let me know.
Here is workaround solution
struct DetailView: View {
#EnvironmentObject var word: Word
#State private var identifier: String = ""
var body: some View {
VStack {
Text(self.identifier)
Button(action: {
self.identifier += "a"
}) {
Text("Click to add an 'a' to Word's identifier")
}
}
.onAppear {
self.identifier = self.word.identifier
}
.onDisappear {
self.word.identifier = self.identifier
}
}
}
This works as expected on iOS 13.4, assuming Word is something like:
class Word : ObservableObject {
#Published var identifier = "foo"
}