How to run another process continuously after dismiss the modal - swift

I want to run another process continuously after dismiss the modal.
In the following code, the modal is not dismissed and only the subsequent processing is performed.
How can I do that?
I used NotificationCenter and callbacks, all with the same result.
struct HomeView: View {
#State private var modalPresented: Bool = false
var body: some View {
VStack {
Button(action: {}) {
Text("setting")
}.sheet(isPresented: self.$modalPresented) {
SettingView(onDismiss: {
self.modalPresented = false
})
}
}
}
}
struct SettingView: View {
var onDismiss: () -> ()
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
// The following is the logout function.
logout()
}) {
Text("logout")
}
}
}
}

do you mean like so?
struct ContentView: View {
#State private var modalPresented: Bool = false
var body: some View {
VStack {
Button(action: {
self.modalPresented.toggle()
}) {
Text("setting")
}
.sheet(isPresented: self.$modalPresented) {
SettingView()
// .onDismiss: {
// self.modalPresented = false
// }
}
}
}
}
struct SettingView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
print("logout")
}) {
Text("logout")
}
}
}
}

Related

SwitUI parent child binding: #Published in #StateObject doesn't work while #State does

I have a custom modal structure coming from this question (code below). Some property is modified in the modal view and is reflected in the source with a Binding. The catch is that when the property is coming from a #StateObject + #Published the changes are not reflected back in the modal view. It's working when using a simple #State.
Minimal example (full code):
class Model: ObservableObject {
#Published var selection: String? = nil
}
struct ParentChildBindingTestView: View {
#State private var isPresented = false
// not working with #StateObject
#StateObject private var model = Model()
// working with #State
// #State private var selection: String? = nil
var body: some View {
VStack(spacing: 20) {
Button("Show child", action: { isPresented = true })
Text("selection: \(model.selection ?? "nil")") // replace: selection
}
.modalBottom(isPresented: $isPresented, view: {
ChildView(selection: $model.selection) // replace: $selection
})
}
}
struct ChildView: View {
#Environment(\.dismissModal) var dismissModal
#Binding var selection: String?
var body: some View {
VStack {
Button("Dismiss", action: { dismissModal() })
VStack(spacing: 0) {
ForEach(["Option 1", "Option 2", "Option 3", "Option 4"], id: \.self) { choice in
Button(action: { selection = choice }) {
HStack(spacing: 12) {
Circle().fill(choice == selection ? Color.purple : Color.black)
.frame(width: 26, height: 26, alignment: .center)
Text(choice)
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
.padding(50)
.background(Color.gray)
}
}
extension View {
func modalBottom<Content: View>(isPresented: Binding<Bool>, #ViewBuilder view: #escaping () -> Content) -> some View {
onChange(of: isPresented.wrappedValue) { isPresentedValue in
if isPresentedValue == true {
present(view: view(), dismissCallback: { isPresented.wrappedValue = false })
}
else {
topMostController().dismiss(animated: false)
}
}
.onAppear {
if isPresented.wrappedValue {
present(view: view(), dismissCallback: { isPresented.wrappedValue = false })
}
}
}
fileprivate func present<Content: View>(view: Content, dismissCallback: #escaping () -> ()) {
DispatchQueue.main.async {
let topMostController = self.topMostController()
let someView = VStack {
Spacer()
view
.environment(\.dismissModal, dismissCallback)
}
let viewController = UIHostingController(rootView: someView)
viewController.view?.backgroundColor = .clear
viewController.modalPresentationStyle = .overFullScreen
topMostController.present(viewController, animated: false, completion: nil)
}
}
}
extension View {
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.shared.windows.first!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
}
private struct ModalDismissKey: EnvironmentKey {
static let defaultValue: () -> Void = {}
}
extension EnvironmentValues {
var dismissModal: () -> Void {
get { self[ModalDismissKey.self] }
set { self[ModalDismissKey.self] = newValue }
}
}
struct ParentChildBindingTestView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
ParentChildBindingTestView()
}
}
}
The changes are reflected properly when replacing my custom structure with a fullScreenCover, so the problem comes from there. But I find it surprising that it works with a #State and not with a #StateObject + #Published. I thought those were identical.
If having #StateObject is a must for your code, and your ChildView has to update the data back to its ParentView, then you can still make this works around #StateObject.
Something like this:
struct Parent: View {
#StateObject var h = Helper()
var body: some View {
TextField("edit child view", text: $h.helper)
Child(helper: $h.helper)
}
}
struct Child: View {
#Binding var helper: String
var body: some View {
Text(helper)
}
}
class Helper: ObservableObject {
#Published var helper = ""
}
I think your can get anwser here
with #State we use onChange because it uses for only current View
with #Published we use onReceive because it uses for many Views
#State should be used with #Binding
#StateObject with #ObservedObject
In your case, you would pass the model to the child view and update it's properties there.

Pop to RootView from SheetView

I found this tutorial to pop to a RootView through a series of NavigationLinks.
I would like to do the same thing but pop to the RootView in a SheetView.
This is what the structure of my app is like:
struct RootController: View {
#State var isPresented = true
var body: Some View {
NavigationView {
FirstView()
}
.environment(\.rootPresentationMode, self.$isPresented)
}
}
struct FirstView: View {
#Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode>
var body: Some View {
NavigationLink(destination: SecondView,
isActive: self.$pushNewProject) {
EmptyView()
}
.isDetailLink(false)
.hidden()
}
}
struct SecondView: View {
#Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode>
#State private var isShowingSheetView:Bool = false
var body: Some View {
Text(...)
.sheet(isPresented: $isShowingEditProjectSheet){
SheetView(showingSheetView: self.$isShowingSheetView)
}
}
}
And my SheetView (where I want to trigger the pop back to the RootView):
struct SheetView: View {
#Environment(\.rootPresentationMode) private var rootPresentationMode
var body: Some View {
Text()
.alert("Are you sure you want to delete?", isPresented: $showingDeleteAlert){
Button("Cancel", role: .cancel){
}
Button("Delete"){
print("attempt to pop")
self.rootPresentationMode.wrappedValue.dismiss()
}
}
}
}
after adding this extension
struct RootPresentationModeKey: EnvironmentKey {
static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}
extension EnvironmentValues {
var rootPresentationMode: Binding<RootPresentationMode> {
get { return self[RootPresentationModeKey.self] }
set { self[RootPresentationModeKey.self] = newValue }
}
}
typealias RootPresentationMode = Bool
extension RootPresentationMode {
public mutating func dismiss() {
self.toggle()
}
}
Any ideas how to pop to root from a SheetView?

SwiftUI Executing a method from a view after confirmation from the subview

Is it technically possible to call a method from a view after getting the confirmation from the subview ? I could call it from the SubStruct if I pass the viewModel and item to it, but I am just curious about the code below which results in
Segmentation Fault: 11
import SwiftUI
struct ContentView: View {
var body: some View {
MainStruct(viewModel: MyViewModel())
}
}
struct MainStruct: View {
#StateObject var viewModel: MyViewModel
#State var functionToPass: ()
let items = ["test1", "test2", "test2"]
var body: some View {
ForEach(items, id: \.self) { (item) in
Text(item).onTapGesture {
functionToPass = viewModel.deleteItem(name: item)
}
}
SubStruct(passedFunction: {functionToPass})
}
struct SubStruct: View {
var passedFunction: () -> Void
var body: some View {
Button(action: {passedFunction()}, label: {
Text("confirm deletion")
})
}
}
}
class MyViewModel: ObservableObject {
func deleteItem(name: String) {
print(name)
///deletion logic
}
}
Try this :
struct MainStruct: View {
#StateObject var viewModel: MyViewModel
#State private var functionToPass: () -> Void = {}
let items = ["test1", "test2", "test2"]
var body: some View {
VStack {
ForEach(items, id: \.self) { item in
Text(item)
.onTapGesture {
functionToPass = {
viewModel.deleteItem(name: item)
}
}
}
SubStruct(passedFunction: functionToPass)
}
}
struct SubStruct: View {
var passedFunction: () -> Void
var body: some View {
Button(action: {
passedFunction()
}, label: {
Text("confirm deletion")
})
}
}
}

passing parameter to a SwiftUI Sheet

I need to pass a parameter calledFrom to a Sheet in SwiftUI.
Strangely, the parameter is not used on the first call, but it works on the following ones.
import SwiftUI
struct ContentView: View {
#State var showSheet = false
#State var calledFrom = -1
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(isPresented: $showSheet) { Dialog(calledFrom: calledFrom) }
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){print("Button \(i) pressed"); calledFrom = i; showSheet = true }
}
}
struct Dialog: View {
var calledFrom : Int
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack{
Text("Called from Button \(calledFrom)")
Button("close"){presentationMode.wrappedValue.dismiss()}
}
.padding()
}
}
You have to use sheet(item:) to get the behavior you're looking for. In iOS 14, the sheet view is calculated before the #State changes:
struct ActiveItem : Identifiable {
var calledFrom: Int
var id: Int { return calledFrom }
}
struct ContentView: View {
#State var activeItem : ActiveItem?
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(item: $activeItem) { item in
Dialog(calledFrom: item.calledFrom)
}
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){
print("Button \(i) pressed");
activeItem = ActiveItem(calledFrom: i)
}
}
}

How to initialize a Binding : Bool variable in SwiftUI?

How to initialize shouldPopToRootView? Here is my code:
import SwiftUI
struct DoctorHomePage: View {
#Binding var shouldPopToRootView : Bool
init() {
UINavigationBar.appearance().backgroundColor = .clear
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
}
var body: some View {
NavigationView {
VStack {
Text("Hello, World!")
}
}
}
}
check this out:
struct DoctorHomePage: View {
#Binding var shouldPopToRootView : Bool
init(shouldPopToRootView: Binding<Bool>) {
self._shouldPopToRootView = shouldPopToRootView
UINavigationBar.appearance().backgroundColor = .clear
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
} // I get the error here
var body: some View {
NavigationView {
VStack {
Text("Hello, World!")
}
}
}
}
struct ContentView: View {
var body: some View {
DoctorHomePage(shouldPopToRootView: .constant(true))
}
}