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.
Make sure
ParentViewModel
has references toViewModelA
andViewModelB
(as I understand it you're already there).Give
ViewModelA
aSelectedRowChanged
event:public event EventHandler SelectedRowChanged;
Give
ViewModelB
anIsMyButtonEnabled
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));
}
}In
ViewB
, bind the button'sIsEnabled
property to that property:<Button
...
IsEnabled="{Binding IsMyButtonEnabled}"
...Give both
ViewModelA
andViewModelB
aSelectedRow
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.
*/
}
}ParentViewModel
sets up a handler forViewModelA.SelectedRowChanged
, which it can do because it has a reference to its instance ofViewModelA
. Then the handler tellsViewModelB
to do whatever needs to be done when SelectedRow changes. Another way to do this would be to giveViewModelB
a reference toViewModelA
, and have it handleViewModelA.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
Write Data to Firebase in The Background After Retrieving Steps with Healthkit's Background Delivery
Is There an Object Class in Swift
Sklabelnode Text with Two Different Fonts and Colour. How Is This Possible
How to Disabled Some Default Functionality in Scene View When Allowscameracontrol = True
Pfuser That Created Object Cannot Delete Object Despite Acl Writing Access
Thread Safety of Method Calls on "Shared" Static Constant Property
Change The 2Nd and 3Rd Pickerview Acording to What Row from The 1St Picker Is Selected
How to Update User Interface on Core Data
Overlaying Image on Video Reduces Video Resolution
Swift Nsbundle Pathforresource() Returns Nil
Add Assets to an Icloud Shared Photo Album Programmatically
Uialertview or UIalertcontroller to Display Only Once in Swift
Osx/Swift: Call a Function When Internet Connection Is Established/Restored
Verifying The Purchase (Receipt) of Another Application (Mac App Store)
Set Maximum Characters (To One) in a Nstextfield in Swift
Xcode Swift: Could Not Insert New Outlet Connection
Get Out of Navigation Controller and Go Back to Tab Bar View