Where to remove Notification observer in Xamarin iOS? - ios

I am registering for Notifications in ViewDidLoad() in my Xamarin project. However I don't see any method where I can "Remove" the Observer. I need to execute something even when the View is not visible, so I can't give it in ViewWillAppear. Is there anything similar to "deinit" method.
Tried out "dispose" method in C#, but it didn't get called.
Even after doing a "PopViewController" the observer is still not getting unregistered. Not sure why it is not getting released from the memory even when the view controller is removed.

Depending on your needs you could override ViewWillAppear and/or ViewDidDisappear to add/remove it there.
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
// subscribe
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
// unsubscribe
}
Keep in mind ViewDidDisappear will be called when a new controller is pushed on top of your current controller and ViewWillAppear will get called again when that new controller is popped, which might not be suitable for whatever you're trying to do.

You can do all subscription for events in ViewDidLoad and unsubscribing them would happen automatically from IOs. If you really feel to unsubscribe them, then do it in Dispose method.
Here's an example,
In the ViewController,
// Subscribing
public override void ViewDidLoad()
{
_homeView.HomeButton.TouchUpInside += OnHomeButtonClicked;
}
// Unsubscribing
protected override void Dispose(bool disposing)
{
_secondView.SecondButton.TouchUpInside -= OnSecondButtonClicked;
base.Dispose(disposing);
}

Related

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

Monotouch - tableview inside UIViewController: override functions don't get called

I have a tableview inside a UIViewController. I also have some dynamic data, which I would like to load onto this tableview. I have attempted two approaches so far:
I created a nested private class inside this UIViewController, which inherits from UITableViewSource. I then made a new instance of this class in ViewDidLoad and assigned it to my table view's Source property.
For this option, the override functions are called. However,I'm loading the lists dynamically in the ViewWillAppear, so the data source at this point is empty.
I tried creating a new instance of the nested class in the ViewWillAppear, but the override functions aren't called when I do this. They're only called if I do it in theViewDidLoad.
The other option I tried was implementing the IUITableViewDelegate and the IUITableViewDataSource in my UIViewController class. Then in my ViewDidLoad assigned "this" to both the WeakDelegate and WeakDataSource properties of my table view.
The challenge I'm facing with this second option is : I keep getting this error:
[HomePageController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x185ce440
I've added this export attribute to the function, but it doesn't solve the issue:
[Export ("tableView:numberOfRowsInSection:")]
public nint RowsInSection (UITableView tableView, nint section)
{
return 1;
}
I prefer the first option, but why can't I initialize the class in the ViewWillAppear after loading my data? I need to call it here, since the ViewDidLoad is called only once.
This is what I've done:
public async override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
try {
LoadingOverlay loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds, "Loading...");
this.View.AddSubview (loadingOverlay);
await FetchAllData ();
loadingOverlay.Hide ();
if (tableViewNotice != null) {
if(allData != null)
{
if(allData.Count > 0)
{
homeDataSource = new HomeDataSource(this);
tableViewNotice.Source = homeDataSource;
}
}
}
} catch (Exception ex) {
Console.WriteLine (ex.Message + ex.StackTrace);
}
}
When UITableView be loaded at draw to the layer,it will load datas just like when viewDidLoad has been called. But the tableView's datasource is set in viewWillAppear, that's too late. so you need call reloadData() after you set datasource or set datasource in viewDidLoad function.

Monotouch/iOS: which place is the best one to unsubscribe the delegate

The best method in iOS to subscribe a event is ViewDidLoad, but when dismiss the view , the ViewDidUnload() is not called(only when the memory warning.)
Which place is the best to unsubscribe the event?
(In the subviewController I subscribe a event that reference the MainViewController, When open the subview twice, I receive two event trigger because the unsubscribe in viewdidunload() is never called.)
How about with subscribe/unsubscribe in ViewWillAppear/ViewWillDisapper?
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
this.mBL.OrderChanged += HandleOrderChanged;
}
public override void ViewWillDisappear (bool animated)
{
base.VieWillDisappear (animated);
if (this.mBL!=null)
this.mBL.OrderChanged -= HandleOrderChanged;
}
Use ViewDidLoad and ViewDidUnload, those are the appropriate places to subscribe/unsubscribe events from the UI.
Here is a general article on memory management in iOS that I think applies here: http://www.buildingiphoneapps.com/buildingiphoneapps/2010/6/25/memory-management-and-viewdidunload.html
Now, if you're not wanting to have the event run when your View is not visible, do something like this in the event handler:
if (IsViewLoaded && View.Window != null) {
//code here
}
I've found this is the easiest way to tell if the view is on screen.
I agree, the best time is in ViewWillAppear/ViewWillDisappear, not ViewDidUnload.
ViewDidUnload is called called post iOS 6:
https://developer.xamarin.com/api/member/UIKit.UIViewController.ViewDidUnload()/

Wait for PresentModalViewController to finish (monotouch)

I would like to wait for a controller being displayed with PresentModalViewController() to finish its job before resuming execution (like modal dialogs in WinForms). How can this be done with monotouch?
I know there is a similar question on SO but the answer is for Objective-C and, frankly, I don't get it.
Many thanks.
EDIT
Here's the first setup I tried and which didn't seem to work:
Create a new Project (Single View Application);
Add two controllers (iPhone View Controller): FirstController and SecondController; the first controller overrides DismissModalViewControllerAnimated and fires a OnFirstFinished event just after being dismissed;
In the main controller:
...
public partial class TestModalViewController : UIViewController
{
private UIButton button;
private FirstController first;
private SecondController second;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
button = UIButton.FromType(UIButtonType.RoundedRect);
button.Frame = new RectangleF(0, 0, 100, 50);
button.SetTitle("Test", UIControlState.Normal);
button.TouchUpInside += PresentFirstController;
View.Add(button);
PresentFirstController(null, null);
}
void PresentFirstController (object sender, EventArgs e)
{
bool firstFinished = false;
first = new FirstController();
first.OnFirstFinished += delegate(object s, EventArgs args) {
firstFinished = true;
};
this.PresentModalViewController(first, true);
do
{
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
} while (!firstFinished);
second = new SecondController();
this.PresentModalViewController(second, true);
}
// ...
}
In FirstController:
...
public override void DismissModalViewControllerAnimated (bool animated)
{
base.DismissModalViewControllerAnimated (animated);
if(null != OnFirstFinished)
{
OnFirstFinished(this, null);
}
}
In this setup the execution blocks (black loading screen) and the first controller isn't loaded.
If the call to PresentFirstController() is removed from ViewDidLoad(), the main controller loads fine and when clicking the "Test" button the first controller is loaded. However, after the first controller is dismissed, the second controller is NOT loaded - iOS doesn't seem to like presenting a modal controller right after dismissing another one. This can be solved by adding a small delay (but how small is still safe?) like below:
public override void DismissModalViewControllerAnimated (bool animated)
{
base.DismissModalViewControllerAnimated (animated);
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow(0.2));
if(null != OnFirstFinished)
{
OnFirstFinished(this, null);
}
}
In general, the code pattern that you want to use on iOS is to chain these actions. For example, your view controller would likely have a login/password field, and a button to do the login.
What you would do is connect an action to the Login button that contacts the server, validates the user and if the credentials are OK, dismiss the dialog view controller and at that point resume execution.
That said, you could present the view controller and run the UI main loop manually and wait for some event to trigger before resuming execution.
do {
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
} while (!done);
You can save yourself some pain and structure your code with the UIKit patterns instead of trying to fight them. You will end up saving time.

BeginAnimations and CommitAnimations with NavigationController.PushViewController

I'm trying to get a basic flip animation transition working when I push a controller inside a navigation. The code below flips the view, however the view appears first (each element fades in), and then the flip occurs. Is it possible to do a flip animation with a UINavigationController?
Any pointers would be great, the examples I've found for Monotouch are performing animations on Views inside another view.
void ToolbarButtonClick()
{
InformationController controller = new InformationController();
NavigationController.PushViewController(controller,true);
}
public class InformationController : UIViewController
{
public override void ViewDidLoad ()
{
UIView.BeginAnimations("Flip");
UIView.SetAnimationDuration(1.0);
UIView.SetAnimationTransition(UIViewAnimationTransition.FlipFromRight,View,true);
base.ViewDidLoad ();
Title = "Information";
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
UIView.CommitAnimations();
}
}
I was sort of there, but the View needs to be taken from the NavigationController:
// Push the controller first or the Title doesn't animate
NavigationController.PushViewController(controller,false);
UIView.BeginAnimations(null,IntPtr.Zero);
UIView.SetAnimationDuration(1);
UIView.SetAnimationTransition(UIViewAnimationTransition.FlipFromLeft,
NavigationController.View,true);
UIView.CommitAnimations();
I'm not an expert, but I'm wondering about that TRUE on the PushViewcontroller which indicates that it is going to be animated. It has me wondering if that is making the NavigationController do the initial amount of animation work, which is then followed by yours. When you set it to false, what happens? I know we tend to automatically put the TRUE in there without thinking.

Resources