0

My goal is to have custom modals present over an root view that is essentially a tabbed view. So, I wrapped the TabView in a ZStack and am using an ObservableOBject. But I don't feel I'm doing it the right way.

In my other file, I have the Custom modal "subviews" which has an enum, too, which I think is the right approach to take. But I cannot figure out how to dismiss a modal after it is visible.

It must be @EnvironmentObject, but I don't know what if anything to put in the scene delegate, etc. ("Hacking with Swift" is failing me here, although it's a great resource.)

My idea is that views from the tabbed view will have various buttons which present different modal views, populated later with data specific to say a user and set of fields for data entry.

Right now, I just want to understand how to present and dismiss them.

Here is my root view

import SwiftUI

struct ContentView: View {

    @ObservedObject var modal = CustomModal()

    var body: some View {

        ZStack {
            TabView {
                ZStack {
                Color.pink.opacity(0.2)
                        Button(action: {
                            withAnimation{
                                self.modal.visibleModal = VisibleModal.circle
                            }
                        }) {
                            Text("Circle").font(.headline)
                        }
                        .frame(width: 270, height: 64)
                        .background(Color.pink.opacity(0.5)).foregroundColor(.white)
                        .cornerRadius(12)
                }
                   .tabItem{
                       VStack{
                       Image(systemName: "1.square.fill")
                       Text("One")
                       }
                    }.tag(1)

                   ZStack {
                   Color.blue.opacity(0.2)
                       Button(action: {
                        self.modal.visibleModal = VisibleModal.squircle
                       }) {
                           Text("Square").font(.headline)
                       }
                       .frame(width: 270, height: 64)
                       .background(Color.blue.opacity(0.5)).foregroundColor(.white)
                       .cornerRadius(12)
                       }
                   .tabItem{
                          VStack{
                          Image(systemName: "2.square.fill")
                          Text("Two")
                          }
                       }.tag(2)
               }.accentColor(.purple)
            VStack {
              containedView()
            }
        }
    }

    func containedView() -> AnyView {
        switch modal.visibleModal {
           case .circle: return AnyView(CircleView())
           case .squircle: return AnyView(SquircleView())
           case .none: return AnyView(Text(""))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

And here is my second file with the enum and "subview" modals

import SwiftUI

class CustomModal: ObservableObject {
    @Published var visibleModal: VisibleModal = VisibleModal.none
}

enum VisibleModal {
    case circle, squircle, none
}

struct CircleView: View {
    var body: some View {
        ZStack {
            Color.pink.blur(radius: 0.4)
            Circle().fill()
            .frame(width: 300)
                .foregroundColor(Color.white.opacity(0.75))
            dismissButton()
        }.edgesIgnoringSafeArea(.all)
    }
}

struct SquircleView: View {
    var body: some View {
        ZStack{
            Color.green.blur(radius: 0.4)
            RoundedRectangle(cornerRadius: 48, style: .continuous)
                .frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
            dismissButton()
        }.edgesIgnoringSafeArea(.all)
    }
}

struct dismissButton: View {

    @ObservedObject var modal = CustomModal()

    var body: some View {
        VStack{
            Spacer()
            Button(action: {
                self.modal.visibleModal = VisibleModal.none

            }) {
                Text("Dismiss").font(.headline)
            }
            .frame(width: 270, height: 64)
            .background(Color.white.opacity(0.35)).foregroundColor(.white)
            .cornerRadius(12)
            .padding(.bottom, 44)
        }
    }
}

Rillieux
  • 587
  • 9
  • 23

2 Answers2

0

Are you just trying to pass your observable object to the new view?

func containedView() -> some View {
        switch modal.visibleModal {
           case .circle: return CircleView()
                    .environmentObject(self.modal)
           case .squircle: return SquircleView()
                    .environmentObject(self.modal)
           case .none: return Text("")
        }
    }

Unless I am misunderstanding the question.

Michael Wells
  • 586
  • 6
  • 14
  • I'm not sure what you r code does. When I inserrt the line `.environmentObject(self.modal)` Xcode tells me to add on `as! AnyView` but then when I run it, it crashes with `Could not cast value of type`. My current code shows the modals correctly. But I cannot figure out what to put in the dismiss button on the modal view to dismiss it. I likely need some parameter in the button, too? – Rillieux Mar 25 '20 at 21:00
  • Quick question why AnyView instead of having containedView() return some view? – Michael Wells Mar 25 '20 at 21:18
  • That I found in an answer here: https://stackoverflow.com/questions/56736466/alternative-to-switch-statement-in-swiftui-viewbuilder-block – Rillieux Mar 25 '20 at 22:04
0

Okay, after a lot of fiddling, it works.

Now my code is as follows.

Root view

struct ContentView: View {

    @EnvironmentObject var isModalVisible: CustomModal
    @ObservedObject var modal = CustomModal()

    var body: some View {

        ZStack {
            TabView {
                ZStack {
                Color.pink.opacity(0.2)
                        Button(action: {
                            withAnimation{
                                self.isModalVisible.isModalVisible.toggle()
                                self.modal.currentModal = VisibleModal.circle
                            }
                        }) {
                            Text("Circle").font(.headline)
                        }
                        .frame(width: 270, height: 64)
                        .background(Color.pink.opacity(0.5)).foregroundColor(.white)
                        .cornerRadius(12)
                }
                   .tabItem{
                       VStack{
                       Image(systemName: "1.square.fill")
                       Text("One")
                       }
                    }.tag(1)
                   ZStack {
                   Color.blue.opacity(0.2)
                       Button(action: {
                        self.isModalVisible.isModalVisible.toggle()
                        self.modal.currentModal = VisibleModal.squircle
                       }) {
                           Text("Square").font(.headline)
                       }
                       .frame(width: 270, height: 64)
                       .background(Color.blue.opacity(0.5)).foregroundColor(.white)
                       .cornerRadius(12)
                       }
                   .tabItem{
                          VStack{
                          Image(systemName: "2.square.fill")
                          Text("Two")
                          }
                       }.tag(2)
               }.accentColor(.purple)
            if self.isModalVisible.isModalVisible {
                VStack {
                  containedView()
                }
            }
        }
    }

    func containedView() -> AnyView {
        switch modal.currentModal {
           case .circle: return AnyView(CircleView())
           case .squircle: return AnyView(SquircleView())
           case .none: return AnyView(Text(""))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(CustomModal())
    }
}


and the second file with the supporting views and classes and enums:

import SwiftUI

class CustomModal: ObservableObject {
    @Published var isModalVisible = false
    @Published var currentModal: VisibleModal = .none
}

enum VisibleModal {
    case circle, squircle, none
}

struct CircleView: View {
    @EnvironmentObject var env: CustomModal

    var body: some View {
        ZStack {
            Color.pink.blur(radius: 0.4)
            Circle().fill()
            .frame(width: 300)
                .foregroundColor(Color.white.opacity(0.75))
            dismissButton()
        }.edgesIgnoringSafeArea(.all)
    }
}

struct SquircleView: View {
    var body: some View {
        ZStack{
            Color.green.blur(radius: 0.4)
            RoundedRectangle(cornerRadius: 48, style: .continuous)
                .frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
            dismissButton()
        }.edgesIgnoringSafeArea(.all)
    }
}

struct dismissButton: View {

    @EnvironmentObject var env: CustomModal

    var body: some View {
        VStack{
            Spacer()
            Button(action: {
                self.env.isModalVisible.toggle()
                print("TAPPED")
            }) {
                Text("Dismiss").font(.headline)
            }
            .frame(width: 270, height: 64)
            .background(Color.white.opacity(0.35)).foregroundColor(.white)
            .cornerRadius(12)
            .padding(.bottom, 44)
        }
    }
}

It still can be refactored. I'm sure. I'd also be happy to hear any comments on how to improve it. But it seems to work.

NOTE: This code ContentView().environmentObject(CustomModal()) is put in the previewP{rovider code and in SceneDelegate.

Rillieux
  • 587
  • 9
  • 23
  • I've noticed that on a physical device, the `Color.pink.opacity(0.2)` in the TabView ZStack doesn't toggle correctly. Anyone stopping by here have any idea why? – Rillieux Mar 25 '20 at 22:19