Swiftui How to Pass Data from Child to Parent as Done in C# with The 'Delegate-Eventhandler-Eventargs' Way

SwiftUI How to pass data from child to parent as done in C# with the 'Delegate-EventHandler-EventArgs' way

Here is possible solution. Tested with Xcode 11.4 / iOS 13.4

struct ChildView: View {
var function: (String) -> Void

@State private var value = "Child Value"
var body: some View {
Button(action: {
self.function(self.value)
}, label: {
Text("Button")
})
}
}

struct ContentView: View {
var body: some View {
ChildView { self.setViewBackToNil(myStringParameter: $0) }
}

func setViewBackToNil(myStringParameter: String) {
print("I am the parent: \(myStringParameter)")
}
}

Pass data from code behind and ViewModel to another ViewModel

There's a lot of typing here, but there's no difficult logic and there's nothing that'll turn into a bug and bite you.

  1. Make sure ParentViewModel has references to ViewModelA and ViewModelB (as I understand it you're already there).

  2. Give ViewModelA a SelectedRowChanged event:

    public event EventHandler SelectedRowChanged;
  3. Give ViewModelB an IsMyButtonEnabled property (but name it better than that; you need the name to indicate which button its referring to)

    private bool _isMyButtonEnabled;
    public bool IsMyButtonEnabled {
    get { return _isMyButtonEnabled; }
    set {
    _isMyButtonEnabled = value;
    OnPropertyChanged(nameof(IsMyButtonEnabled));
    }
    }
  4. In ViewB, bind the button's IsEnabled property to that property:

    <Button
    ...
    IsEnabled="{Binding IsMyButtonEnabled}"
    ...
  5. Give both ViewModelA and ViewModelB a SelectedRow property.

    private SomeClass _selectedRow;
    public SomeClass SelectedRow {
    get { return _selectedRow; }
    set {
    _selectedRow = value;
    OnPropertyChanged(nameof(SelectedRow));
    /*
    DO ADDITIONAL STUFF HERE:

    VMA:
    SelectedRowChanged?.Invoke(this, EventArgs.Empty);

    VMB:
    Set IsMyButtonEnabled to whatever is appropriate based on
    the selected row.
    */
    }
    }
  6. ParentViewModel sets up a handler for ViewModelA.SelectedRowChanged, which it can do because it has a reference to its instance of ViewModelA. Then the handler tells ViewModelB to do whatever needs to be done when SelectedRow changes. Another way to do this would be to give ViewModelB a reference to ViewModelA, and have it handle ViewModelA.SelectedRowChanged. But as a rule, you want your child viewmodels to be more loosely coupled than that. You don't want B to depend on having a sibling of type A. But the parent already depends on having both of those children. In for a penny, in for a pound. You can't get any more wet.

    ...
    // Constructor or someplace
    this.VMA = new ViewModelA();
    this.VMA.SelectedRowChanged += ViewModelA_SelectedRowChanged;
    ...

    void ViewModelA_SelectedRowChanged(Object sender, EventArgs e)
    {
    // VMB.SelectedItem's setter will enable the button appropriately
    VMB.SelectedItem = VMA.SelectedItem;
    }

You really "should" be doing the button thing with a Command, and enabling the Command instead of having an Is*ButtonEnabled property. That's a more versatile and powerful way to do things. But we can take things one step at a time.

Detect when a presented view controller is dismissed

According to the docs, the presenting controller is responsible for the actual dismiss. When the presented controller dismisses itself, it will ask the presenter to do it for it. So if you override dismissViewControllerAnimated in your VC1 controller I believe it will get called when you hit cancel on VC2. Detect the dismiss and then call the super classes version which will do the actual dismiss.

As found from discussion this does not seem to work. Rather than rely on the underlying mechanism, instead of calling dismissViewControllerAnimated:completion on VC2 itself, call dismissViewControllerAnimated:completion on self.presentingViewController in VC2. This will then call your override directly.

A better approach altogether would be to have VC2 provide a block which is called when the modal controller has completed.

So in VC2, provide a block property say with the name onDoneBlock.

In VC1 you present as follows:

  • In VC1, create VC2

  • Set the done handler for VC2 as: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • Present the VC2 controller as normal using [self presentViewController:VC2 animated:YES completion:nil];

  • In VC2, in the cancel target action call self.onDoneBlock();

The result is VC2 tells whoever raises it that it is done. You can extend the onDoneBlock to have arguments which indicate if the modal comleted, cancelled, succeeded etc....



Related Topics



Leave a reply



Submit