I am using StoryBoards in Monotouch and have a ViewController which has been designated as the Initial VC. I have a second VC that I want to display programmatically from the ViewDidLoad method of the first VC. The Objective-C steps are like so
Instantiate second VC via Storyboard.InstantiateViewController("SecondVC")
Set its ModalTransitionalStyle
Set its delegate to self like secondVC.delegate = self;
use this.PresentViewController(secondVC, true, nil)
How do I accomplish that in MonoTouch C# code?
the VC that I instantiate using Storyboard.Ins.. method does not have a delegate or Delegate property to set. And although my code compiles, I do not see the second view. I see only the first View
Any help highly appreciated
thanks
You should be able to do this if you call it on a delay. I used a Threading.Timer to call PresentViewController a second after load. As far as the delegate, a UIViewController does not have that property. You will have to cast to the controller type that is applicable to the controller you are loading. Then you can set the delegate. You will probably want to set the WeakDelegate instead of the Delegate if you want to use this (self).
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Timer tm = new Timer (new TimerCallback ( (state)=> {
this.InvokeOnMainThread (new NSAction (()=> {
UIStoryboard board = UIStoryboard.FromName ("MainStoryboard", null);
UIViewController ctrl = (UIViewController)board.InstantiateViewController ("Number2VC");
ctrl.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
this.PresentViewController (ctrl, true, null);
}));
}), null, 1000, Timeout.Infinite);
}
Related
I am using MvvmCross and using ShowViewModel to navigate between view. It takes the default navigation behavior i.e., slides from Right->Left . I want few view controllers to slide from bottom to top . Can someone let me know how we can do this in MvvmCross. These are not overlays but regular view controllers.
You can achieve this by setting a custom delegate for your Navigation Controller. This can be done by override following method in your custom ViewPresenter
protected override void OnMasterNavigationControllerCreated()
{
this.MasterNavigationController.WeakDelegate = new NavigationControllerDelegate();
}
Within this delegate you can set your transitions, e.g.:
public class NavigationControllerDelegate : UINavigationControllerDelegate
{
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForOperation(UINavigationController navigationController, UINavigationControllerOperation operation, UIViewController fromViewController, UIViewController toViewController)
{
if (operation == UINavigationControllerOperation.Push)
{
if (fromViewController is MenuViewController)
{
return new BottomToTopTransition();
}
...
}
}
}
BottomToTopTransition is also a custom class and inherits from UIViewControllerAnimatedTransitioning. Last step is to override AnimateTransition() in this transition class and you are done.
I want to use a third party view controller that already inherits from UIViewController (https://bitbucket.org/thedillonb/monotouch.slideoutnavigation/src/f4e51488598b/MonoTouch.SlideoutNavigation?at=master), how would I integrate that with MVVMCross?
I could just take the source and change it to inherit from MvxViewController, but guessing I will run into this with other libraries.
Do I need to implement all the interfaces MvxViewController does? IMvxTouchView? IMvxEventSourceViewController?
For this particular case, where you don't actually want to do any data-binding so you can just use a custom presenter - e.g. see #Blounty's answer, or see this project demo - https://github.com/fcaico/MvxSlidingPanels.Touch
If you ever do need to convert third party ViewController base classes so that they support data-binding, then the easiest way is exactly what you guessed:
inherit from them to provide an EventSource-ViewController
inherit from the EventSource-ViewController to add the Mvx BindingContext
This technique is exactly how MvvmCross itself extends each of UIViewController, UITableViewController, UITabBarController, etc in order to provide data-binding.
For example, see:
extending UIViewController to provide an eventsource - MvxEventSourceViewController.cs
extending the event source ViewController to provide a binding context - MvxViewController.cs
Note that because C# doesn't have any Multiple-Inhertiance or any true Mixin support, this adaption of ViewControllers does involve a little cut-and-paste, but we have tried to minimise this through the use of event hooks and extension methods.
If it helps, this iOS technique for a previous MvvmCross version was discussed in Integrating Google Mobile Analytics with MVVMCross (obviously this is out of date now - but the general principles kind of remain the same - we adapt an existing viewcontroller via inheritance)
In Android, a similar process is also followed for Activity base classes - see ActionBarSherlock with latest MVVMCross
You can use a custom view presenter like below, This is pretty much straight out of my app using the SlideOutNavigation.
public class Presenter
: IMvxTouchViewPresenter
{
private readonly MvxApplicationDelegate applicationDelegate;
private readonly UIWindow window;
private SlideoutNavigationController slideNavigationController;
private IMvxTouchViewCreator viewCreator;
public Presenter(MvxApplicationDelegate applicationDelegate, UIWindow window)
{
this.applicationDelegate = applicationDelegate;
this.window = window;
this.slideNavigationController = new SlideoutNavigationController();
this.slideNavigationController.SlideWidth = 200f;
this.window.RootViewController = this.slideNavigationController;
}
public async void Show(MvxViewModelRequest request)
{
var creator = Mvx.Resolve<IMvxTouchViewCreator>();
if (this.slideNavigationController.MenuView == null)
{
// TODO: MAke this not be sucky
this.slideNavigationController.MenuView = (MenuView)creator.CreateView(new MenuViewModel());
((MenuView) this.slideNavigationController.MenuView).MenuItemSelectedAction = this.MenuItemSelected;
}
var view = creator.CreateView(request);
this.slideNavigationController.TopView = (UIViewController)view;
}
public void ChangePresentation(MvxPresentationHint hint)
{
Console.WriteLine("Change Presentation Requested");
}
public bool PresentModalViewController(UIViewController controller, bool animated)
{
Console.WriteLine("Present View Controller Requested");
return true;
}
public void NativeModalViewControllerDisappearedOnItsOwn()
{
Console.WriteLine("NativeModalViewControllerDisappearedOnItsOwn");
}
private void MenuItemSelected(string targetType, string objectId)
{
var type = Type.GetType(string.Format("App.Core.ViewModels.{0}ViewModel, AppCore", targetType));
var parameters = new Dictionary<string, string>();
parameters.Add("objectId", objectId);
this.Show(new MvxViewModelRequest { ViewModelType = type, ParameterValues = parameters });
}
}
I have an app with a medium-sized storyboard, which is complicated enough for me not to want to mess around with it too much.
I want to copy this storyboard and change the color scheme and let the user select which color scheme to use.
My question is: can I programmatically select which storyboard will be used by default on startup? If yes - how do I do that?
I looked at a somewhat related question: Storyboards Orientation Support in Xcode 4.5 and iOS 6.x ?
Based on that code I made an extension method:
static bool IsStoryboardLoading {get;set;}
public static T ConsiderSwitchingStoryboard<T> (this UIViewController from) where T: UIViewController
{
if (!IsStoryboardLoading && LocalStorage.Instance.IsWhiteScheme && false) {
try {
IsStoryboardLoading = true;
UIStoryboard storyboard = UIStoryboard.FromName ("MainStoryboard_WHITE", NSBundle.MainBundle);
T whiteView = storyboard.InstantiateViewController (typeof(T).Name) as T;
from.PresentViewController (whiteView, false, null);
return whiteView;
} finally {
IsStoryboardLoading = false;
}
}
return null;
}
}
and then I use it in ViewDidAppear override:
public override void ViewDidAppear (bool animated)
{
this.ConsiderSwitchingStoryboard<MyViewController> ();
}
This code works in some cases but in others it causes an error when performing a push segue:
NSGenericException Reason: Could not find a navigation controller for segue 'segSearchResults'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:void_objc_msgSendSuper_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
It might be simpler to just use 1 Storyboard and have 2 sets of controllers in the same storyboard. Just use different storyboard ids for the controllers. You can use the same class on those if needed.
For example:
var whiteController = Storyboard.InstantiateViewController("MyWhiteController") as MyController;
var blueController = Storyboard.InstantiateViewController("MyBlueController") as MyController;
Both could create an instance of MyController, but pull out different layouts from the same storyboard file.
Another option is to use UIAppearance to dynamically set a "style" on all controls of a certain type in your app.
For example, to set the default UIBarButtonItem image throughout your app:
UIBarButtonItem.Appearance.SetBackgroundImage(UIImage.FromFile("yourpng.png"), UIControlState.Normal, UIBarMetrics.Detault);
(You might check my parameters there)
I'm subclassing a StringElement in MonoTouch.Dialog.
In there I can attach to the Tapped event or I can override Selected().
Both fire if I tap the element.
However, Selected() is giving me access to the DialogViewController the element is a member of, where this information is not passed to the Tapped event.
What is the logic here? Is an element supposed to know its DialogViewController or not? If yes: how to get tho the controller from the Tapped event then?
Found out myself by looking at the source on Github.
The only place where Tapped event is triggered, is from Selected(). So I think Tapped should really by of type EventHandler instead of Action.
public override void Selected (DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
if (Tapped != null)
Tapped ();
tableView.DeselectRow (indexPath, true);
}
At the time I wrote that code, the idea was simply that with lambdas, you can pass whatever state you need to your Tapped handler, without using the object/EventArgs pattern.
So you would do something like:
var dialogViewController = CreateDvC ();
new StringElement ("....", () => {
// reference any variables here
// my container is:
Console.Writeline (dialogViewController);
}
I am new to Monotouch and attempting to understand how some of the basics hang together. Hopefully someone out there will be able to assist.
I've created a test project in MonoDevelop based on the Multi-Screened Apps tutorial on the Xamarin site and have extended it to include a tableView. I am having issues with referencing the Navigation Controller in a view that I need to push a detail view onto to display the detail of an item tapped in the table via an accessory button. I know some of the coding is scrappy, just been trying to get it working at this stage rather than the clarity in the code! (I'm using the latest versions of all Mono tools/libraries etc and XCode 4 on Lion). Starting at the beginning here's the code in FinishedLaunching in AppDelegate.
window = new UIWindow (UIScreen.MainScreen.Bounds);
this.rootNavigationController = new UINavigationController();
// Create a new homescreen
HomeScreen homeScreen = new HomeScreen();
// Add the homescreen to the root navigation controller
this.rootNavigationController.PushViewController(homeScreen, false);
// Set the root view controller on the window - the navigation controller
// will handle the rest.
this.window.RootViewController = this.rootNavigationController;
// make the window visible
this.window.MakeKeyAndVisible ();
homeScreen just contains a button which loads a new view containing a table view (OrderList). Here's the button event handler.
void HandleOrdersButtonhandleTouchUpInside (object sender, EventArgs e)
{
if (orderListScreen == null)
orderListScreen = new OrderList();
NavigationController.PushViewController(orderListScreen, true);
}
This all works fine. I've got some dummy data that loads into the table view, which also works fine. OrderData is a simple class for testing which just contains a couple of properties. I've added an AccessoryButton to the cells and am trying to load a detail view when this is tapped. Here's the code that does this - comment in code where issue is! (I'd previously tested the AccessoryButtonTapped functionilty was working by just displaying an alert).
public override void AccessoryButtonTapped (UITableView tableView, NSIndexPath indexPath)
{
var dataSource = (OrdersTableViewDataSource)tableView.DataSource;
if (detailScreen == null)
detailScreen = new OrderDetailScreen();
OrderData theOrder = dataSource.OrdersData[indexPath.Row];
detailScreen.currentOrder = theOrder;
// Cant get a reference to NavigationController here to push the detail view!
// this.NavigationController is not available
this.NavigationController.PushViewController(detailScreen, true);
}
My understanding of NavigationControllers from what I've read so far is that this reference should be available through all views that originate from the root ViewController/NavigationController without the need to pass the reference from AppDelegate through the various view constructors?
Can anyone tell me what I might be missing here?
Thanks in advance.
** An update after reviewing Jason's comment: (Please let me know if this is the incorrect way to post updates)
So, I tried the following:
I saved a reference to the NavigationController in the constructor for the ViewController that contains the table view as follows:
public partial class OrderList : UIViewController
{
UINavigationController navController;
public OrderList () : base ("OrderList", null)
{
this.Title = "Orders";
navController = this.NavigationController;
}
Then passed that into the TableViewDelegate, where the AccessoryButtonTapped is handled, in the ViewDidLoad method.
public override void ViewDidLoad ()
{
orderTableView.DataSource = new OrdersTableViewDataSource();
orderTableView.Delegate = new OrdersTableViewDelegate(navController);
base.ViewDidLoad ();
}
Then referenced that in the TableViewDelegate:
public class OrdersTableViewDelegate : UITableViewDelegate
{
UINavigationController navController;
public OrdersTableViewDelegate(UINavigationController controller)
{
navController = controller;
}
// Rest of class definition
}
Then the reference to the NavigationController using navController compiles with the code as previously described using the following in the AccessoryButtonTapped method:
navController.PushViewController(detailScreen, true);
When I run this and tap on the AccessoryButton I get a null reference exception on navController. The reference to this.NavigationController in the ViewController constructor is null. Am I doing something in the wrong place or sequence?
Cheers
The NavigationController property is on your table's view controller. If you are trying to reference it from your table's datasource, you need to pass a reference to the controller when you create the datasource.