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.
}
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 have a problem with refreshing a UITableView correctly, at least the first time of ViewWillAppear and sometimes (rarely) might even hang the app. When I navigate away from the UIViewController with changing tab item and navigate back, everything starts working perfectly for the entire lifecycle..
Having a ReactiveTableViewSource like below.
public class ATableViewSource : ReactiveTableViewSourceBase<IAViewModel>
{
WeakReference<AListViewController> _weakContainer;
Lazy<AListViewController> _lazyContainerViewController;
AListViewController Container => _lazyContainerViewController.Value;
public ATableViewSource(AListViewController container,
UITableView tableView,
IReactiveNotifyCollectionChanged<IAViewModel> collection)
: base(tableView, collection,
ATableViewCell.Key,
ATableViewCell.Height,
ATableViewCell.Height)
{
_weakContainer = new WeakReference<AListViewController>(container);
tableView.RegisterNibForCellReuse(ATableViewCell.Nib, ATableViewCell.Key);
_lazyContainerViewController = new Lazy<AListViewController>(() =>
{
AListViewController _container;
_weakContainer.TryGetTarget(out _container);
return _container;
});
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
base.RowSelected(tableView, indexPath);
tableView.DeselectRow(indexPath, false);
var item = ItemAt(indexPath) as IAViewModel;
if (item.IsNotNull())
{
AViewController viewController = new AViewController(item);
Container.NavigationController.PushViewController(viewController, true);
}
}
}
In AListViewController.ViewDidLoad I have this setup.
ATableViewSource _viewSource;
public override void ViewDidLoad()
{
base.ViewDidLoad();
TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 350.0f;
TableView.ContentInset = new UIEdgeInsets(8.0f, 0.0f, 8.0f, 0.0f);
_viewSource = new ATableViewSource(this, TableView, ViewModel.TheReactiveList);
TableView.Source = _viewSource;
}
And in ViewWillAppear I always refresh the data (ViewModel.TheReactiveList).
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
ViewModel.RefreshTheReactiveList();
}
The AViewModel setup.
public class TheListViewModel : SchedulersViewModelBase, ITheListViewModel
{
public ReactiveList<IAViewModel> TheReactiveList { get; } = new ReactiveList<IAViewModel> { ChangeTrackingEnabled = true };
protected readonly IDataService DataService;
public TheListViewModel(IScheduler mainScheduler,
IScheduler taskPoolScheduler,
IDataService dataService)
: base(mainScheduler, taskPoolScheduler)
{
DataService = dataService;
}
public void RefreshTheReactiveList()
{
DataService.RefreshData()
.SubscribeOn(MainScheduler)
.ObserveOn(MainScheduler)
.Subscribe(ClearDataAndAddRange,
ex => AppObservables.Errors.OnNext(ex))
.DisposeWith(Disposables);
}
void ClearDataAndAddRange(IEnumerable<IAViewModel> data)
{
using (TheReactiveList.SuppressChangeNotifications())
{
TheReactiveList.Clear();
TheReactiveList.AddRange(data);
}
}
}
I have to mention in this use case I have a parent UIViewController with two child controllers each with the same setup (UITableView, ReactiveTableViewSource, ViewModel.ReactiveList) and Hidden state is controlled for their UIView containers, but I noticed similar effects to UIViewController with one UITableView taking up to 3 seconds to show the results in the UITableView.
For your reference I am posting the ReactiveTableViewSourceBase<TViewModel> below, I have found this long time ago on the internet, so it might be suspicious. Changing the base class to a ReactiveTableViewSource<TViewModel> doesn't really make any difference though.
public abstract class ReactiveTableViewSourceBase<TViewModel> : ReactiveTableViewSource<TViewModel>, IInformsEnd
{
private readonly Subject<Unit> _requestMoreSubject = new Subject<Unit>();
private readonly Subject<CGPoint> _scrollSubject = new Subject<CGPoint>();
public IObservable<CGPoint> DidScroll
{
get { return _scrollSubject.AsObservable(); }
}
public IObservable<Unit> RequestMore
{
get { return _requestMoreSubject; }
}
public override void Scrolled(UIScrollView scrollView)
{
_scrollSubject.OnNext(scrollView.ContentOffset);
}
~ReactiveTableViewSourceBase()
{
Console.WriteLine("Destorying " + GetType().Name);
}
protected ReactiveTableViewSourceBase(UITableView tableView, nfloat height, nfloat? heightHint = null)
: base(tableView)
{
tableView.RowHeight = height;
tableView.EstimatedRowHeight = heightHint ?? tableView.EstimatedRowHeight;
}
protected ReactiveTableViewSourceBase(UITableView tableView, IReactiveNotifyCollectionChanged<TViewModel> collection,
Foundation.NSString cellKey, nfloat height, nfloat? heightHint = null, Action<UITableViewCell> initializeCellAction = null)
: base(tableView, collection, cellKey, (float)height, initializeCellAction)
{
tableView.RowHeight = height;
tableView.EstimatedRowHeight = heightHint ?? tableView.EstimatedRowHeight;
}
public override void WillDisplay(UITableView tableView, UITableViewCell cell, Foundation.NSIndexPath indexPath)
{
if (indexPath.Section == (NumberOfSections(tableView) - 1) &&
indexPath.Row == (RowsInSection(tableView, indexPath.Section) - 1))
{
// We need to skip an event loop to stay out of trouble
BeginInvokeOnMainThread(() => _requestMoreSubject.OnNext(Unit.Default));
}
}
public override void RowSelected(UITableView tableView, Foundation.NSIndexPath indexPath)
{
var item = ItemAt(indexPath) as ICanGoToViewModel;
if (item != null)
{
item.GoToCommand.Execute();
}
base.RowSelected(tableView, indexPath);
}
protected override void Dispose(bool disposing)
{
_requestMoreSubject.Dispose();
_scrollSubject.Dispose();
base.Dispose(disposing);
}
}
public interface IInformsEnd
{
IObservable<Unit> RequestMore { get; }
}
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 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);