Handling Unbalanced calls to begin / end appearance when popping controller - ios

I'm trying to add some error handling (to cope with loses of network connectivity when initializing my view models as well as elsewhere) by having it publish a message that is picked up by my view models that will then do a ChangePresentation with a PresentationHint that causes my presenter (derived from MvxTouchViewPresenter) to do this:
this.MasterNavigationController.PopToRootViewController(false);
This occasionally works, but a lot of the time it doesn't, getting stuck on whatever view it was currently on and I see the message Unbalanced calls to begin/end appearance transitions for <MyView: 0x...>. I believe this is because sometimes the message is getting thrown before the view that was loading has had time to finish loading (the actual loading of data is asynchronous and fires up on another thread - hence the problem).
So my question is, is there a way to synchronize this so that instead of immediately popping to the root, it will finish what it's doing, then pop to the root? Or is there some better way to handle this?

There's not quite enough code in your question to work out what's happening. There are lots of other questions on StackOverflow containing that error message - it may be worth looking through them to see if there is a nice solution to your issue. e.g. UINavigationController popToRootViewController, and then immediately push a new view
If you do want to detect 'finish loading' then on way to do this is to listen to the ViewDidAppear messages in the ViewControllers that are being shown. In mvx, this is easy to do as all ViewController's support a ViewDidAppearCalled event that you could easily hook up in your custom presenter:
private readonly Queue<Action> _pendingActions = new Queue<Action>();
private bool _isBusy;
public override void Show(Cirrious.MvvmCross.Touch.Views.IMvxTouchView view)
{
if (_isBusy)
{
_pendingActions.Enqueue(() => Show(view));
return;
}
_isBusy = true;
var eventSource = view as IMvxEventSourceViewController;
eventSource.ViewDidAppearCalled += OnViewAppeared;
base.Show(view);
}
private void OnViewAppeared(object sender, MvxValueEventArgs<bool> mvxValueEventArgs)
{
_isBusy = false;
var eventSource = sender as IMvxEventSourceViewController;
eventSource.ViewDidAppearCalled -= OnViewAppeared;
if (!_pendingActions.Any())
return;
var action = _pendingActions.Dequeue();
action();
}
public override void ChangePresentation(Cirrious.MvvmCross.ViewModels.MvxPresentationHint hint)
{
if (_isBusy)
{
_pendingActions.Enqueue(() => ChangePresentation(hint));
return;
}
base.ChangePresentation(hint);
}
Note: this code requires 3.0.13 or later to work (there was a bug for ViewDidAppear in some view controllers in earlier versions)
If you are using a simple UINavigationController, another way to achieve a similar effect is to use the Delegate on that controller - see https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationControllerDelegate_Protocol/Reference/Reference.html

Related

ViewModel Live Data observers calling on rotation

In my view model, I have two properties:
private val databaseDao = QuestionDatabase.getDatabase(context).questionDao()
val allQuestions: LiveData<List<Question>> = databaseDao.getAllQuestions()
I have observers set on "allQuestions" in my fragment and I'm noticing the observer is being called when I rotate the device. Even though the View Model is only being created once (can tell via a log statement in init()), the observer methods are still being called.
Why is this? I would think the point is to have persistency in the View Model. Ideally, I want the database questions to be only loaded once, regardless of rotation.
This happens because LiveData is lifecycle aware.
And When you rotate the screen you UI Controller [Activity/Fragment] goes through various lifecycle states and lifecycle callbacks.
And since LiveData is lifecycle aware, it updates the detail accordingly.
I have tried to explain this with following points:
When the UI Controller is offscreen, Live Data performs no updates.
When the UI Controller is back on screen, it gets current data.
(Because of this property you are getting above behavior)
When UI controller is destroyed, it performs cleanup on its own.
When new UI Controller starts observing live data, it gets current data.
add this check inside observer
if(lifecycle.currentState == Lifecycle.State.RESUMED){
//code
}
I have the same issue, after reading the jetpack guideline doc, I solve it. Just like what #SVK mentioned, after the rotation of the screen, activity/fragment were re-created.
Base on the solution https://stackoverflow.com/a/64062616,
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun observeForever(observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observeForever { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
#MainThread
override fun setValue(#Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}

Pass data back to uiviewcontroller on pop (NavigationController.PopViewController)

I'm developing an iOS application using Xamarin.iOS (Code only, no storyboards) and I wonder what the best way to send data back to the original uiviewcontroller when I pop from the navigationcontroller.
In android I use StartActivityForResult and then override OnResult, but I can't find a similar way for iOS.
I know there's overrides for ViewDidLoad, ViewDidAppear, etc, what I'm looking for is some kind of ViewDidGetPoppedBackTo (hope you get it).
Or is there another better way to achieve this?
NavigationController keeps track of all the ViewControllers as an array: NavigationController.ViewControllers
You can get an existing instance of the ViewController Type from this array via following code:
(You may write this method in BaseViewController if you have it.)
public T InstanceFromNavigationStack<T> () where T : UIViewController
{
return (T)NavigationController.ViewControllers.FirstOrDefault(v => v is T);
}
Then use it like :
var myVCInstance = InstanceFromNavigationStack<MyTargetViewController>();
if(myVCInstance != null)
{
//Assign a value like
myVCInstance.MyVariable = "MyValue";
//Or call a method like
myVCInstance.MethodToReloadView("MyValue")
}
//Go Back Navigation Code
//Then here write your navigation logic to go back.
This not only helps passing data in Previous ViewController, but Any ViewController in the stack. Simply pass the Type of it to get an Instance from Stack.
NOTE: This should work if your Navigation stack doesn't have multiple instance of the same ViewController Type.
Use this way
ViewController viewController = (ViewController)NavigationController.TopViewController;
viewController.SendData(myevent);
Create method SendData in your ToViewController this method is called first when navigationg back and your data send to your previous ViewController.
Another option that I've started using is EventHandler methods. Here is an example I use to populate a UITextField in the parent view controller with the selection from a UITableView (child view controller) and then close the child.
Define the EventHandler method in your parent view controller:
void LocationLookup_OnSelected(object sender, EventArgs e)
{
chosenLocation = (MKPlacemark)sender;
planLocation.Text = chosenLocation.Name;
this.ParentViewController.DismissViewController(true, null);
}
Pass the EventHandler method from the parent as a property of the child.
public partial class LocationLookupViewController : UITableViewController
{
private event EventHandler OnSelected;
public LocationLookupViewController(EventHandler OnSelected)
{
this.OnSelected = OnSelected;
}
...
}
Call the EventHandler passing in the object / data that you need in the parent
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
...
OnSelected(response?.MapItems[0].Placemark, new EventArgs());
}
Note - The above class and function are incomplete but should provide you with an idea of how this technique works.

memory management issues in xamarin.ios

As far as I have worked in xamarin declaring global variables in view controller is very important or else it can be garbage collected when it comes to Binding and all concerned.
likewise for nsnotification we have to have a global reference of nsobject type and remove the nsobject when we don't need nsnotification.
there are no practical documentation available and making native iOS developers to get frustrated with xamarin.ios
Suggesting few things like this would be a good help for any ios developer who becomes xamarin.ios developer
In Every ViewControllers Write following Code:
public override void ViewDidDisappear (bool animated)
{
//Executed when we navigate to other view, moving this ViewController in Navigation stack
//1. UnSubscribe All Events Here (Note: These must be SubScribed back in ViewWillAppear)
btnLogin.TouchupInside -= OnLoginClicked
//2. Remove Any TapGuestures (Note: These must be added back in ViewWillAppear)
if (singleTapGuesture != null)
{
scrollView.RemoveGestureRecognizer(singleTapGuesture);
singleTapGuesture.Dispose();
singleTapGuesture = null;
}
//3. Remove any NSNotifications (Note: These must be added back in ViewWillAppear)
//4. Clear anything here, which again initializes in ViewWillAppear
if (ParentViewController == null) {
//This section will be executed when ViewController is Removed From Stack
//1. Clear everything here
//2. Clear all Lists or other such objects
//3. Call Following Method which will clear all UI Components
ReleaseDesignerOutlets ();
}
base.ViewDidDisappear (animated);
}
This will clear out all unwanted object when ViewController is Navigated or Removed from the stack.
For UITableViews, use WeakDataSource & WeakDelegate.
There can be many more based on following Links:
Ref 1
Ref 2

Swift iOS, UITableView nil

I have a Swift (2.2) iOS app (my first) with a couple of UITableViews. One of the views lists payments which are added / removed throughout the life of the program.
This all works fine 99% of the time, but a few times I have come across an issue where the UITableView all of a sudden becomes nil.
The IBOutlet must be hooked up correctly, or it would not work at all.
What could possibly be causing this when I am not assigning to the IBOutlet variable anywhere (just calling methods on it)?
Or (if I cannot find the cause), advice on best handling when it happens (if I need to recreate it, what about outlets, events, autolayout, etc.?)
#IBOutlet weak var paymentTableView: UITableView!
func handlePayment(payment: PaymentRecord) -> Void {
let existingPaymentIndex = payments.indexOf({ $0.payNo == payment.payNo })
if (existingPaymentIndex != nil) {
payments.removeAtIndex(existingPaymentIndex!)
}
if (self.paymentTableView == nil) { // Here is where I notice the issue
Log.error?.message("handlePayment: paymentTableView is nil!!")
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.paymentTableView.reloadData()
})
}
What is happening is that the tableview is being unloaded from memory when the view is disposed of. You likely have an asynchronous task completing, most usually from making a network call, that is returning and calling this function after the view was disposed. I've seen this happen many times when a view is trying to load a network resource and the user is able to switch to a different view before the network call's completion handler is called.

MvvmCross and UICollectionView how to bind SelectedItem from VM to View

I'm using MvvmCross with UICollectionView.
Bindings work perfectly, I have all my data properly displayed, and even if I select an item in CollectionView it gets properly set in my ViewModel.
For SelectedItem I use the following binding:
set.Bind(_collectionViewSource).For(x => x.SelectedItem).To(vm => vm.SelectedMachine);
The only problem I have is that I want a first CollectionViewItem to be selected initially.
As the sources of MvvmCross say that's not supported currently (in the setter for SelectedItem):
// note that we only expect this to be called from the control/Table
// we don't have any multi-select or any scroll into view functionality here
So, what's the best way to perform initial pre-selection of an item? What's the place I can call _collectionView.SelectItem from?
I tried calling it when collection changes, but that doesn't seem to work.
If you need this functionality, you should be able to inherit from MvxCollectionViewSource and to add a property something like
public event EventHandler SelectedItemExChanged;
public object SelectedItemEx
{
get { return base.SelectedItem; }
set
{
base.SelectedItem = value;
var index = FindIndexPath(value); // find the NSIndexPath of value in the collection
if (index != null)
_collectionView.SelectItem(index, true, UICollectionViewScrollPosition.CenteredHorizontally);
var handler = SelectedItemExChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
That can then be bound instead of SelectedItem
What's the place I can call _collectionView.SelectItem from? I tried calling it when collection changes, but that doesn't seem to work.
If that doesn't work, then I'm not sure - you are probably heading into animation timing problems - see questions like uicollectionview select an item immediately after reloaddata? - maybe try editing your question to post a bit more of your code - something that people can more easily hope with debugging.

Resources