I can create a simple Tab Renderer which will update my Forms ToolBarItems to use the built in iOS icons like below.
NavigationController is only NOT NULL in ViewWillAppear If I try it in ViewDidLoad, it is NULL.
The problem with this is you get a flash of the TabBar Item text before it gets replaced with the actual icon.
Is there a different place I should be intercepting the ToolBar behavior?
[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabRenderer))]
namespace Cellar.iOS.Renders
{
public class TabRenderer : TabbedRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var list = new List<UIBarButtonItem>();
foreach (var item in NavigationController.TopViewController.NavigationItem.RightBarButtonItems)
{
if (string.IsNullOrEmpty(item.Title))
{
continue;
}
if (item.Title.ToLower() == "add")
{
var newItem = new UIBarButtonItem(UIBarButtonSystemItem.Add)
{
Action = item.Action,
Target = item.Target
};
list.Add(newItem);
}
if (list.Count > 0)
NavigationController.TopViewController.NavigationItem.RightBarButtonItems = list.ToArray();
}
}
}
}
override OnElementChanged method:
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.NewElement!= null)
{
var list = new List<UIBarButtonItem>();
// Your code goes here
}
}
Yes, you should override the PushViewController and inherit from NavigationRenderer method. Here is what I am using in my application:
public class CustomToolbarRenderer : NavigationRenderer
{
public override void PushViewController(UIViewController viewController, bool animated)
{
base.PushViewController(viewController, animated);
List<UIBarButtonItem> newItems = new List<UIBarButtonItem>();
foreach (UIBarButtonItem i in TopViewController.NavigationItem.RightBarButtonItems)
{
if (i.Title != null)
{
if (i.Title.Equals(Constants.Toolbar.Add))
{
var newItem = new UIBarButtonItem(UIBarButtonSystemItem.Add);
newItem.Action = i.Action;
newItem.Target = i.Target;
newItems.Add(newItem);
}
else if (i.Title.Equals(Constants.Toolbar.Camera))
{
var newItem = new UIBarButtonItem(UIBarButtonSystemItem.Camera);
newItem.Action = i.Action;
newItem.Target = i.Target;
newItems.Add(newItem);
}
else if (i.Title.Equals(Constants.Toolbar.Delete))
{
var newItem = new UIBarButtonItem(UIBarButtonSystemItem.Trash);
newItem.Action = i.Action;
newItem.Target = i.Target;
newItems.Add(newItem);
}
else
newItems.Add(i);
}
else
newItems.Add(i);
}
TopViewController.NavigationItem.RightBarButtonItems = newItems.ToArray();
}
}
Try the below code at the end or after the execution of the required code block...
return base.ViewWillAppear(animated);
Related
I have a TableView where the user can check multiple rows.
Now I have the problem that when I select e.g. the 5th element the 27th element in the list is automatically checked too, but I cant explain why.
I´m using the following Code for the TableViewSource base class:
public abstract class BaseChecklistTableViewSource : UITableViewSource
{
protected int checkCount;
public BaseChecklistTableViewSource()
{
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.CellAt(indexPath);
if (indexPath.Row >= 0)
{
if (cell.Accessory == UITableViewCellAccessory.None)
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
checkCount++;
}
else if (cell.Accessory == UITableViewCellAccessory.Checkmark)
{
cell.Accessory = UITableViewCellAccessory.None;
checkCount--;
}
CheckCheckCount();
}
}
protected void CheckCheckCount()
{
if (checkCount > 0)
{
EnableDoneButton();
}
else if (checkCount == 0)
{
DisableDoneButton();
}
}
protected abstract void EnableDoneButton();
protected abstract void DisableDoneButton();
}
This is the code of the concrete class of "BaseChecklistTableViewSource":
public partial class CheckunitTableViewSource : BaseChecklistTableViewSource
{
ListCheckunitController controller;
private IEnumerable<Checkunit> existingCheckunits;
public CheckunitTableViewSource(ListCheckunitController controller)
{
this.controller = controller;
this.existingCheckunits = new List<Checkunit>();
}
public CheckunitTableViewSource(ListCheckunitController controller, IEnumerable<Checkunit> existingCheckunits) : this(controller)
{
if (existingCheckunits == null || !existingCheckunits.Any())
{
throw new ArgumentNullException(nameof(existingCheckunits));
}
this.existingCheckunits = existingCheckunits;
checkCount = this.existingCheckunits.Count();
CheckCheckCount();
}
// Returns the number of rows in each section of the table
public override nint RowsInSection(UITableView tableview, nint section)
{
if (controller.Checkunits == null)
{
return 0;
}
return controller.Checkunits.Count();
}
//
// Returns a table cell for the row indicated by row property of the NSIndexPath
// This method is called multiple times to populate each row of the table.
// The method automatically uses cells that have scrolled off the screen or creates new ones as necessary.
//
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell("checkunitViewCell") ?? new UITableViewCell();
int row = indexPath.Row;
var element = controller.Checkunits.ElementAt(row);
if(existingCheckunits.FirstOrDefault(e => e.Id == element.Id) != null)
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
}
//if(cell.Accessory == UITableViewCellAccessory.Checkmark)
//{
// checkCount++;
//}
cell.TextLabel.Text = $"{element.Order}. {element.Designation}";
cell.Tag = element.Id;
return cell;
}
protected override void EnableDoneButton()
{
controller.EnableDoneButton();
}
protected override void DisableDoneButton()
{
controller.DisableDoneButton();
}
}
I tried to use a breakpoint in the RowSelected method, but the breakpoint will only be hit once!
This is the Code for the TableViewController:
public partial class ListCheckunitController : BaseTableViewController
{
private ImmobilePerpetration masterModel;
private LoadingOverlay loadingOverlay;
public IEnumerable<Checkunit> Checkunits { get; set; }
public IEnumerable<Checkunit> existingCheckunits;
public IEnumerable<CheckpointAssessment> existingAssessments;
public static readonly NSString callHistoryCellId = new NSString("checkunitViewCell");
UIRefreshControl refreshControl;
private UIBarButtonItem doneButton;
public ListCheckunitController(IntPtr handle) : base(handle)
{
existingCheckunits = new List<Checkunit>();
existingAssessments = new List<CheckpointAssessment>();
}
public void Initialize(ImmobilePerpetration masterModel)
{
if (masterModel == null)
{
throw new ArgumentNullException(nameof(masterModel));
}
this.masterModel = masterModel;
}
public void Initialize(ImmobilePerpetration masterModel, IEnumerable<CheckpointAssessment> existingAssessments, IEnumerable<Checkunit> existingCheckunits)
{
Initialize(masterModel);
if(existingAssessments == null || !existingAssessments.Any())
{
throw new ArgumentNullException(nameof(existingAssessments));
}
if (existingCheckunits == null || !existingCheckunits.Any())
{
throw new ArgumentNullException(nameof(existingCheckunits));
}
this.existingAssessments = existingAssessments;
this.existingCheckunits = existingCheckunits;
}
async Task RefreshAsync(bool onlineSync)
{
InvokeOnMainThread(() => refreshControl.BeginRefreshing());
try
{
var syncCtrl = new SynchronizationControllerAsync();
Checkunits = await syncCtrl.SynchronizeCheckUnitAsync(onlineSync).ConfigureAwait(false);
}
catch (Exception e)
{
InvokeOnMainThread(() => { AlertHelper.ShowError(e.Message, this);});
}
InvokeOnMainThread(() =>
{
ListCheckunitTable.ReloadData();
refreshControl.EndRefreshing();
});
}
void AddRefreshControl()
{
refreshControl = new UIRefreshControl();
refreshControl.ValueChanged += async (sender, e) =>
{
await RefreshAsync(true).ConfigureAwait(false);
};
}
public override async void ViewDidLoad()
{
var bounds = UIScreen.MainScreen.Bounds;
loadingOverlay = new LoadingOverlay(bounds);
View.Add(loadingOverlay);
AddRefreshControl();
await RefreshAsync(false).ConfigureAwait(false);
InvokeOnMainThread(() =>
{
doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (s, e) =>
{
PerformSegue("ListCheckpointsSegue", this);
});
doneButton.Enabled = false;
CheckunitTableViewSource source = null;
if(existingCheckunits.Any())
{
source = new CheckunitTableViewSource(this, existingCheckunits);
}
else
{
source = new CheckunitTableViewSource(this);
}
ListCheckunitTable.Source = source;
ListCheckunitTable.Add(refreshControl);
loadingOverlay.Hide();
this.SetToolbarItems(new UIBarButtonItem[] {
new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace) { Width = 50 }
, doneButton
}, false);
this.NavigationController.ToolbarHidden = false;
});
}
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
if (segue.Identifier == "ListCheckpointsSegue")
{
var controller = segue.DestinationViewController as ListCheckpointController;
IList<Checkunit> markedCategories = new List<Checkunit>();
for (int i = 0; i < ListCheckunitTable.NumberOfRowsInSection(0); i++)
{
var cell = ListCheckunitTable.CellAt(NSIndexPath.FromItemSection(i, 0));
if(cell != null)
{
if(cell.Accessory == UITableViewCellAccessory.Checkmark)
{
var originalObject = Checkunits.Where(e => e.Id == cell.Tag).SingleOrDefault();
if(originalObject != null)
{
markedCategories.Add(originalObject);
}
else
{
//TODO: Handle error case!
}
}
}
}
if (markedCategories.Any())
{
if (controller != null)
{
if(existingAssessments.Any() && existingCheckunits.Any())
{
controller.Initialize(masterModel, markedCategories, existingAssessments, existingCheckunits);
}
else
{
controller.Initialize(masterModel, markedCategories);
}
}
}
else
{
//TODO: Print out message, that there are no marked elements!
}
}
}
public void DisableDoneButton()
{
doneButton.Enabled = false;
}
public void EnableDoneButton()
{
doneButton.Enabled = true;
}
}
The used base class only handles a common sidebar view nothing more.
Does anyone have an idea what is going wrong here?
Well, I ended up in creating a bool Array that stores the checked elements.
I tried a demo app from Github from Xamarin and I had the exact same problem.
This leads me to the conclusion that this must be a Xamarin Bug!
I want to replace HockeyApp SDK with App Center SDK. But when I remove the following line of code var manager = BITHockeyManager.SharedHockeyManager; the following unhandled exception occurs on startup: [NSURL isAdtechEvent]: unrecognized selector sent to instance. I have no clue how Adtech is related to HockeySDK. And there is no method or event isAdtechEvent or similar in my code. What can I do to narrow down this error?
Here you'll find the AppDelegate class:
using System;
using System.Threading;
using MvvmCross.Platform;
using MvvmCross.iOS.Platform;
using MvvmCross.iOS.Views.Presenters;
using MvvmCross.Core.ViewModels;
using Foundation;
using GoogleConversionTracking.Unified;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using MTiRate;
using PCLStorage;
using PushNotification.Plugin;
using UIKit;
namespace MyApp
{
public static class ShortcutIdentifier
{
public const string Parkspace = "parkingspace";
}
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate
{
private const string ConversionId = "1054453082";
private const string ConversionLabel = "VaLvCNaO018Q2trm9gM";
private const string ConversionValue = "0.00";
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private static Action _afterPushRegistrationAction;
private UIWindow _window;
// Must override the Window property for iRate to work properly
public override UIWindow Window
{
get => _window;
set => _window = value;
}
static AppDelegate()
{
// rating window configuration
iRate.SharedInstance.DaysUntilPrompt = 0.5f; // default is 10!
iRate.SharedInstance.UsesUntilPrompt = 10;
iRate.SharedInstance.RemindPeriod = 30; // 30 days
iRate.SharedInstance.PromptForNewVersionIfUserRated = false;
iRate.SharedInstance.PromptAtLaunch = false; // trigger prompt manually for it doesn't show on splash screen
// texts
iRate.SharedInstance.MessageTitle = $"{AppResources.RatingMessageTitle} {iRate.SharedInstance.ApplicationName}";
iRate.SharedInstance.Message = AppResources.RatingMessage;
iRate.SharedInstance.RateButtonLabel = AppResources.RatingRateButton;
iRate.SharedInstance.RemindButtonLabel = AppResources.RatingRemindButton;
iRate.SharedInstance.CancelButtonLabel = AppResources.RatingCancelButton;
}
public UIApplicationShortcutItem LaunchedShortcutItem { get; set; }
private UIButton _btn;
public static MvxIosViewPresenter IosViewPresenter { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary launchOptions)
{
var shouldPerformAdditionalDelegateHandling = true;
// Get possible shortcut item
if (launchOptions != null)
{
LaunchedShortcutItem = launchOptions[UIApplication.LaunchOptionsShortcutItemKey] as UIApplicationShortcutItem;
shouldPerformAdditionalDelegateHandling = (LaunchedShortcutItem == null);
}
AppCenter.Start(Settings.Default.AppCenterSecretiOS, typeof(Analytics), typeof(Crashes));
_window = new UIWindow(UIScreen.MainScreen.Bounds);
if (_btn == null)
{
var viewController = new UIViewController();
_window.RootViewController = viewController;
var super = viewController.View;
_btn = new UIButton(UIButtonType.Custom)
{
AccessibilityIdentifier = "StartTrigger",
BackgroundColor = UIColor.Red,
TranslatesAutoresizingMaskIntoConstraints = false
};
_btn.SetTitle("StartTrigger", UIControlState.Normal);
super.AddSubview(_btn);
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterX, 1.0f, 1.0f));
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterY, 1.0f, 1.0f));
_btn.TouchDown += (object sender, EventArgs e) =>
{
StartMvvmCross();
_btn.RemoveFromSuperview();
_btn = null;
};
super.BringSubviewToFront(_btn);
}
StartMvvmCross();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
return shouldPerformAdditionalDelegateHandling;
}
private void StartMvvmCross()
{
CrossPushNotification.Initialize<AppleCrossPushNotificationListenerService>();
//Initialize Google Conversion Tracking with respective parameters
ACTReporter reporter = new ACTConversionReporter(ConversionId, ConversionLabel, ConversionValue, "USD", false);
reporter.Report();
IosViewPresenter = new MvxSlidingPanelsTouchViewPresenter(this, _window);
var setup = new Setup(this, IosViewPresenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(3600);
}
public static void AskForPushPermissionsAndRegister(Action continueWith = null)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!UIApplication.SharedApplication.IsRegisteredForRemoteNotifications ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
else
{
if (UIApplication.SharedApplication.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
}
public bool HandleShortcutItem(UIApplicationShortcutItem shortcutItem)
{
var handled = false;
if (shortcutItem == null) return false;
var routing = Mvx.Resolve<IRoutingService>();
switch (shortcutItem.Type)
{
case ShortcutIdentifier.Parkspace:
routing.Route("fzag://shortcut/parking?id=scan");
handled = true;
break;
}
return handled;
}
public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem,
UIOperationHandler completionHandler)
{
completionHandler(HandleShortcutItem(shortcutItem));
}
public override void OnActivated(UIApplication application)
{
// Handle any shortcut item being selected
HandleShortcutItem(LaunchedShortcutItem);
// Clear shortcut after it's been handled
LaunchedShortcutItem = null;
}
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
// will be called if was clicked
if (notification.UserInfo == null || !notification.UserInfo.ContainsKey(FromObject("url")))
return;
var url = notification.UserInfo["url"].ToString();
var normalized = Uri.UnescapeDataString(url);
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
}
public override bool OpenUrl(UIApplication app, NSUrl url, string srcApp, NSObject annotation)
{
var normalized = Uri.UnescapeDataString(url.ToString());
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
return true;
}
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
var settings = Mvx.Resolve<IAppSettingsService>();
settings.PushNotifications = false;
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnErrorReceived(error);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
var appSettings = Mvx.Resolve<IAppSettingsService>();
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!application.IsRegisteredForRemoteNotifications)
{
appSettings.PushNotifications = false;
return;
}
}
else
{
if (application.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None)
{
appSettings.PushNotifications = false;
return;
}
}
var handler = CrossPushNotification.Current as IPushNotificationHandler;
if (handler == null) return;
handler.OnRegisteredSuccess(deviceToken);
if (App.IsInitialized && Mvx.CanResolve<ILoginService>())
{
var loginService = Mvx.Resolve<ILoginService>();
try
{
AsyncHelper.RunSync(() => loginService.UpdateDeviceIdentificationAsync());
}
catch (Exception ex)
{
Log.Error(ex);
}
}
_afterPushRegistrationAction?.Invoke();
}
public override void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings)
{
application.RegisterForRemoteNotifications();
}
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,
UIWindow forWindow)
{
try
{
if (App.IsInitialized && Mvx.CanResolve<IMvxIosViewPresenter>())
{
if (Mvx.Resolve<IMvxIosViewPresenter>() is MvxIosViewPresenter mvxIosViewPresenter)
{
var viewController = mvxIosViewPresenter.MasterNavigationController.TopViewController;
return viewController.GetSupportedInterfaceOrientations();
}
}
}
catch
{
// can be called before Mvx is setup
}
return UIInterfaceOrientationMask.Portrait;
}
public override void PerformFetch(UIApplication application, Action<UIBackgroundFetchResult> completionHandler)
{
var accountStorage = new AppleAccountStorage();
if (!accountStorage.HasAccount)
{
completionHandler(UIBackgroundFetchResult.NoData);
return;
}
var voidMessenger = new VoidMessenger();
var restService = new RestService(AppleCultureService.Instance);
var authenticationService = new AuthenticationService(restService, voidMessenger, accountStorage);
var plannerService = new PlannerService(restService, authenticationService, FileSystem.Current,
new ZipService());
var tripService = new RealmTravelPlannerBookmarkService(new Lazy<IPlannerService>(() => plannerService), voidMessenger, accountStorage);
var command = new ProfileDataUpdateCommand(tripService, accountStorage, authenticationService,
plannerService);
try
{
AsyncHelper.RunSync(() => command.UpdateAsync(CancellationToken.None));
}
catch (Exception)
{
completionHandler (UIBackgroundFetchResult.Failed);
return;
}
completionHandler(UIBackgroundFetchResult.NewData);
}
}
}
I am doing this: https://developer.xamarin.com/guides/ios/user_interface/controls/tables/creating-tables-in-a-storyboard/
but I can't get public override void PrepareForSegue(UIStoryboardSegue segue, Foundation.NSObject sender)
to get called when I click on a table.
Why?
Has anyone had this problem before?
I download their project and it works, but it does not in my project. Why? I am missing something but I am not sure what it is.
public partial class ViewController : UITableViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
CreateTableItems();
// Add(table);
// Perform any additional setup after loading the view, typically from a nib.
}
public override void PrepareForSegue(UIStoryboardSegue segue, Foundation.NSObject sender)
{
if (segue.Identifier == "TaskSegue")
{ // set in Storyboard
var navctlr = segue.DestinationViewController as wrestingControllor;
if (navctlr != null)
{
var source = table.Source as ScheduleTableViewSource;
var rowPath = table.IndexPathForSelectedRow;
var item = source.GetItem(rowPath.Row);
navctlr.SetTask(this, item); // to be defined on the TaskDetailViewController
}
}
// var page2ViewController = segue.DestinationViewController as Page2ViewController;
// page2ViewController.Name = "dan";
}
void errorMessage(string message)
{
UIAlertView alert = new UIAlertView()
{
Title = "Error",
Message = message,
};
alert.AddButton("OK");
alert.Show();
}
protected void CreateTableItems()
{
bool check = NetworkInterface.GetIsNetworkAvailable();
try
{
if (check)
{
Service1 client = new Service1();
var schools = client.School_lists();
table.Source = new ScheduleTableViewSource(schools, this);
}
else
{
errorMessage("Error no Internet connection");
return;
}
}
catch(Exception e)
{
errorMessage("Error no Internet connection");
return;
}
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
I would like to assure people that I had gone through all the article over internet explaining memory leaks when using button to pop the controller from stack ,and I am aware of strong reference which UIButton creates while triggering events with Lambdas .
I had tried all that none seem to be working for me .
Problem statement
I have a UICollectionViewController as Root ViewController and a floating buttons on top of it which I have created programmatically and added as subviews.
These buttons push viewcontrollers in to stack .
here is the method where I push the controller .
private void HandleClick(object sender, EventArgs e) {
var button = sender as UIButton;
var board = UIStoryboard.FromName("Main", NSBundle.MainBundle);
switch (button.Tag) {
case 3: {
var vc = board.InstantiateViewController("BibleViewController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 5: {
var vc = board.InstantiateViewController("RecordingViewController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 7: {
var vc = board.InstantiateViewController("CameraFbController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 6: {
var vc = board.InstantiateViewController("NewEmojiController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 4: {
var vc = board.InstantiateViewController("WriteNShareController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
default : {
break;
}
}
}
Assume I am pushing BibleViewController (Case 3:)
Please find the code for this controller
public partial class BibleHomeController : UIViewController
{
IList<string> items;
IList<string> item1;
public BibleHomeController() : base("BibleHomeController", null)
{
}
public BibleHomeController(IntPtr handle) : base (handle)
{
}
~BibleHomeController() {
Console.WriteLine("it was called ");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
LoadJson();
tableView.DataSource = new BTableViewDataSource(items);
tableView.Delegate = new TableDelegate(items,this);
tableView.RegisterNibForCellReuse(UINib.FromName("BookCell",NSBundle.MainBundle),BookCell.Key);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
backBtn.TouchUpInside += HandleBackClick;
nwBtn.TouchUpInside += newBtn;
oldBtn.TouchUpInside += oldBtnHanle;
}
private void HandleBackClick(object sender, EventArgs e)
{
this.NavigationController.PopViewController(true);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
backBtn.TouchUpInside -= HandleBackClick;
nwBtn.TouchUpInside -= newBtn;
oldBtn.TouchUpInside -= oldBtnHanle;
backBtn = null;
nwBtn = null;
oldBtn = null;
tableView = null;
}
private void newBtn(object sender, EventArgs e)
{
tableView.DataSource = new BTableViewDataSource(item1);
tableView.Delegate = new TableDelegate(item1,this);
tableView.ReloadData();
}
private void oldBtnHanle(object sender, EventArgs e)
{
tableView.DataSource = new BTableViewDataSource(items);
tableView.Delegate = new TableDelegate(items,this);
tableView.ReloadData();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
private void LoadJson() {
using (StreamReader r = new StreamReader("BibleSection/BibleBooks/Books.json")) {
string json = r.ReadToEnd();
items = JsonConvert.DeserializeObject<List<string>>(json);
}
using (StreamReader r = new StreamReader("BibleSection/BibleBooks/NewBook.json"))
{
string json = r.ReadToEnd();
item1 = JsonConvert.DeserializeObject<List<string>>(json);
}
}
}
public class BTableViewDataSource : UITableViewDataSource
{
IList<string> data;
public BTableViewDataSource(IList<string> list) {
data = list;
}
~BTableViewDataSource() {
Console.WriteLine("it was called ");
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
// if cell is not available in reuse pool, iOS will create one automatically
// no need to do null check and create cell manually
var cell = (BookCell)tableView.DequeueReusableCell("BookCell", indexPath) as BookCell;
cell.PopulateCell(data[indexPath.Row], "");
cell.SetNeedsLayout();
//cell.SeparatorInset = UIEdgeInsets.Zero;
return cell;
}
public override nint RowsInSection(UITableView tableView, nint section)
{
return data.Count;
}
}
public class TableDelegate : UITableViewDelegate {
IList<string> data;
BibleHomeController owner;
public TableDelegate(IList<string> list, BibleHomeController reference)
{
owner = reference;
data = list;
}
~TableDelegate()
{
Console.WriteLine("it was called ");
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//base.RowSelected(tableView, indexPath);
var board = UIStoryboard.FromName("Main", NSBundle.MainBundle);
var vc = (BibleChapterCollectionview)board.InstantiateViewController("BibleChapterCollectionview") as BibleChapterCollectionview;
vc.itemName = data[indexPath.Row];
owner.NavigationController.PushViewController(vc, true);
}
}
My problem is , when I pop the controller in BibleViewController ,
Destructor of none of the classes are called neither dispose is called thereby controller memory is not released .
so everytime i push and pop I add some memory to heap .
I would like to point out , I am detaching all the event handlers from button in viewDidDisappear method .
Could you please help me how to release the resources when I pop the controller .
EDIT :
I had figured the problem is with the tableview.delegate and table.datasource lines.
if I comment them problem is solved .
Should I use weakDelegate?
Modifying this part of code has worked for me .
private void HandleBackClick(object sender, EventArgs e)
{
tableView.Delegate = null;
tableView.DataSource = null;
tableView.Source = null;
this.NavigationController.PopViewController(true);
}
//below modification is not related to problem statement but was needed as buttons need not be nulled
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
backBtn.TouchUpInside -= HandleBackClick;
nwBtn.TouchUpInside -= newBtn;
oldBtn.TouchUpInside -= oldBtnHanle;
}
I have a Tag class inflated from a nib file:
using Foundation;
using System;
using UIKit;
using ObjCRuntime;
namespace TagTest
{
public partial class Tag : UIView
{
public Tag (IntPtr handle) : base (handle)
{
}
public static Tag Create()
{
var arr = NSBundle.MainBundle.LoadNib("Tag", null, null);
var v = Runtime.GetNSObject<Tag>(arr.ValueAt(0));
return v;
}
public override void AwakeFromNib()
{
}
public UIButton Hashtag
{
get
{
return HashtagBtn;
}
}
public UILabel HashtagCount
{
get
{
return HashtagCountLbl;
}
}
}
}
which is used by the following viewmodel
using System.Collections.Generic;
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
using TagTest.Core.ViewModels;
using UIKit;
using Cirrious.FluentLayouts.Touch;
using System;
using System.Drawing;
namespace TagTest
{
public partial class SearchView : MvxViewController
{
List<Tag> _tags;
public SearchView () : base ("SearchView", null)
{
}
new SearchViewModel ViewModel
{
get
{
return (SearchViewModel)base.ViewModel;
}
set
{
base.ViewModel = value;
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
DisplayTags();
View.UserInteractionEnabled = false;
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.TranslatesAutoresizingMaskIntoConstraints = false;
EdgesForExtendedLayout = UIRectEdge.None;
ExtendedLayoutIncludesOpaqueBars = false;
AutomaticallyAdjustsScrollViewInsets = false;
View.ClipsToBounds = true;
View.BackgroundColor = UIColor.Red;
CreateBindings ();
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
ViewModel.Init();
}
void CreateBindings()
{
var set = this.CreateBindingSet<SearchView, SearchViewModel> ();
set.Bind (this).For(x => x.Title).To (vm => vm.Title);
set.Apply ();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
View.LayoutIfNeeded();
}
void DisplayTags()
{
_tags = new List<Tag>();
if (ViewModel.Tags != null)
{
foreach (var item in ViewModel.Tags)
{
_tags.Add(Tag.Create());
}
UIView lastTemplateAdded = View;
for (int i = 0; i < _tags.Count; i++)
{
var tag = _tags[i];
tag.TranslatesAutoresizingMaskIntoConstraints = false;
tag.Hashtag.SetTitle(ViewModel.Tags[i].Tagname, UIControlState.Normal);
tag.HashtagCount.Text = ViewModel.Tags[i].Count.ToString();
tag.Frame = new RectangleF(100f, 300f, 100f, 50f);
tag.Hashtag.Enabled = true;
tag.Hashtag.UserInteractionEnabled = true;
tag.UserInteractionEnabled = true;
tag.Hashtag.TouchUpInside += (sender, e) =>
{
ViewModel.TagSelectedCommand.Execute(tag);
};
View.AddSubview(tag);
if (i == 0)
{
View.AddConstraints(
tag.AtTopOf(View),
tag.AtLeftOf(View),
tag.Height().EqualTo(20)
);
}
else
{
View.AddConstraints(
tag.Below(lastTemplateAdded, 20),
tag.AtLeftOf(View),
tag.Height().EqualTo(20)
);
}
lastTemplateAdded = tag;
}
}
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
}
}
but the Hashtag button is not clickable, the TouchupInside doesn't appear to be fired. If I add a single button to the view it is clickable. What could be going wrong?
Found the problem. It was a combination of setting View.UserInteractionEnabled = false; and using the fluent constraints. I've now set UserInteractioEnabled to true and removed the fluent constraints. All working now.