Swiftui: Understanding .Sheet/.Fullscreencover Lifecycle When Using Constant VS @Binding Initializers

SwiftUI: Understanding .sheet / .fullScreenCover lifecycle when using constant vs @Binding initializers

See comments and print statements. Especially the red

import SwiftUI

struct SegueTest: View {

@State var showSheet: Bool = false{
didSet{
print("showSheet :: didSet")
}
willSet{
print("showSheet :: willSet")
}
}
@State var color: Color = .gray{
didSet{
print("color :: didSet :: \(color.description)")
}
willSet{
print("color :: willSet :: \(color.description)")
}
}
@State var refresh: Bool = false
init(){
print("SegueTest " + #function)
}
var body: some View {
print(#function)
return HStack {
//Just to see what happens when you recreate the View
//Text(refresh.description)
Text(color.description)
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: red")
//Changing the color
color = .red
//Refreshed SegueTest reloads function
//refresh.toggle()
showSheet.toggle()
}

RoundedRectangle(cornerRadius: 25)
.fill(Color.green)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: green")
//Changing the color
color = .green
showSheet.toggle()
}

RoundedRectangle(cornerRadius: 25)
.fill(Color.orange)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: orange")
//Changing the color
color = .orange
showSheet.toggle()
}

}
//This part is likely created when SegueTest is created and since a struct is immutable it keeps the original value
.fullScreenCover(isPresented: $showSheet, content: {
SecondView(selectedColor: color)
})
}
}

struct SecondView: View {

@Environment(\.presentationMode) var presentationMode
//struct is immutable
let selectedColor: Color // Should change to @Binding
init(selectedColor: Color){
print("SecondView " + #function)
self.selectedColor = selectedColor
print("SecondView :: struct :: selectedColor = \(self.selectedColor.description)" )
print("SecondView :: parameter :: selectedColor = \(selectedColor.description)" )
}
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)

RoundedRectangle(cornerRadius: 25)
.fill(selectedColor)
.frame(width: 300, height: 300)
}
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}

}

struct SegueTest_Previews: PreviewProvider {
static var previews: some View {
SegueTest()
}
}

Present a new view in SwiftUI

To show a modal (iOS 13 style)

You just need a simple sheet with the ability to dismiss itself:

struct ModalView: View {
@Binding var presentedAsModal: Bool
var body: some View {
Button("dismiss") { self.presentedAsModal = false }
}
}

And present it like:

struct ContentView: View {
@State var presentingModal = false

var body: some View {
Button("Present") { self.presentingModal = true }
.sheet(isPresented: $presentingModal) { ModalView(presentedAsModal: self.$presentingModal) }
}
}

Note that I passed the presentingModal to the modal so you can dismiss it from the modal itself, but you can get rid of it.



To make it REALLY present fullscreen (Not just visually)

You need to access to the ViewController. So you need some helper containers and environment stuff:

struct ViewControllerHolder {
weak var value: UIViewController?
}

struct ViewControllerKey: EnvironmentKey {
static var defaultValue: ViewControllerHolder {
return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController)

}
}

extension EnvironmentValues {
var viewController: UIViewController? {
get { return self[ViewControllerKey.self].value }
set { self[ViewControllerKey.self].value = newValue }
}
}

Then you should use implement this extension:

extension UIViewController {
func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
toPresent.modalPresentationStyle = style
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, toPresent)
)
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "dismissModal"), object: nil, queue: nil) { [weak toPresent] _ in
toPresent?.dismiss(animated: true, completion: nil)
}
self.present(toPresent, animated: true, completion: nil)
}
}

Finally

you can make it fullscreen like:

struct ContentView: View {
@Environment(\.viewController) private var viewControllerHolder: UIViewController?

var body: some View {
Button("Login") {
self.viewControllerHolder?.present(style: .fullScreen) {
Text("Main") // Or any other view you like
// uncomment and add the below button for dismissing the modal
// Button("Cancel") {
// NotificationCenter.default.post(name: Notification.Name(rawValue: "dismissModal"), object: nil)
// }
}
}
}
}

Presenting View Controller in SwiftUI

As there is no provided related code, so in pseudo-code it would look like the following

struct YourParentView: View {
@State private var presented = false
var body: some View {

// some other code that activates `presented` state

SomeUIElement()
.sheet(isPresented: $presented) {
YourMessageViewControllerRepresentable()
}
}
}

How can I pop to the Root view using SwiftUI?

Setting the view modifier isDetailLink to false on a NavigationLink is the key to getting pop-to-root to work. isDetailLink is true by default and is adaptive to the containing View. On iPad landscape for example, a Split view is separated and isDetailLink ensures the destination view will be shown on the right-hand side. Setting isDetailLink to false consequently means that the destination view will always be pushed onto the navigation stack; thus can always be popped off.

Along with setting isDetailLink to false on NavigationLink, pass the isActive binding to each subsequent destination view. At last when you want to pop to the root view, set the value to false and it will automatically pop everything off:

import SwiftUI

struct ContentView: View {
@State var isActive : Bool = false

var body: some View {
NavigationView {
NavigationLink(
destination: ContentView2(rootIsActive: self.$isActive),
isActive: self.$isActive
) {
Text("Hello, World!")
}
.isDetailLink(false)
.navigationBarTitle("Root")
}
}
}

struct ContentView2: View {
@Binding var rootIsActive : Bool

var body: some View {
NavigationLink(destination: ContentView3(shouldPopToRootView: self.$rootIsActive)) {
Text("Hello, World #2!")
}
.isDetailLink(false)
.navigationBarTitle("Two")
}
}

struct ContentView3: View {
@Binding var shouldPopToRootView : Bool

var body: some View {
VStack {
Text("Hello, World #3!")
Button (action: { self.shouldPopToRootView = false } ){
Text("Pop to root")
}
}.navigationBarTitle("Three")
}
}

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

Screen capture



Related Topics



Leave a reply



Submit