I am writing a small sample app with Xamarin and MvvmCross 6.4.2. I completed the Xamarin.Android version and am now starting the Xamarin.iOS version. I created a view controller for the first screen:
public class SignInViewController : MvxViewController<SignInViewModel>
{
public SignInViewController() : base(nameof(SignInViewController), null)
{
}
public override void ViewDidLoad()
{
// never gets called...
base.ViewDidLoad();
}
}
When I run the app, I just get a blank screen and ViewDidLoad never gets called. In the application output it says:
Failed to instantiate the default view controller for
UIMainStoryboardFile 'Main' - perhaps the designated entry point is
not set?
My Main.storyboard is blank and I tried to modify it in Xcode Interface Builder to set my SignInViewController as the entry point, but I couldn't figure out how.
Failed to instantiate the default view controller for
UIMainStoryboardFile 'Main' - perhaps the designated entry point is
not set?
This error happens due to a simple mistake in your storyboard. When your app starts, iOS needs to know precisely which view controller needs to be shown first – known as your default view controller.
To fix it, add a ViewController to your Main.Storyboard and set it as Inital View Controller.
Refer: how-to-fix-the-error-failed-to-instantiate-the-default-view-controller-for-uimainstoryboardfile
And there are two SignInViewController in your project.
Use this one:
public partial class SignInViewController : UIViewController
{
public SignInViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
// never gets called...
base.ViewDidLoad();
View.BackgroundColor = UIColor.Red;
}
}
Update:
Related
When I was using MvvmCross 5, I coded all my views and avoided using storyboards for my iOS app by writing this in my AppDelegate:
[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate
{
private MvxIosViewPresenter viewPresenter;
public override UIWindow Window
{
get;
set;
}
/// <summary>
/// MvvmCross Mods:
/// - Creates a new presenter, which determines how Views are shown
/// - This example uses a standard presenter.
/// </summary>
/// <param name="application"></param>
/// <param name="launchOptions"></param>
/// <returns></returns>
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
// create a new window instance based on the screen size
Window = new UIWindow(UIScreen.MainScreen.Bounds);
// MvvmCross Mod Start---------------------------------------------
// This class will determine how Views are shown
this.viewPresenter = new MvxIosViewPresenter(this, Window);//new ViewPresenter(Window);
// Init the Setup object, which initializes App.cs
var setup = new Setup(this, this.viewPresenter);
setup.Initialize();
//this.viewPresenter.PresentModalViewController(new ListenViewController(), true);
// Use IoC to find and start the IMvxAppStart object
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
// MvvmCross Mod End--------------------------------------------------
// make the window visible
Window.MakeKeyAndVisible();
return true;
}
public class Setup : MvxIosSetup
{
public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter)
: base(appDelegate, presenter)
{
}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override IMvxTrace CreateDebugTrace()
{
return new DebugTrace();
}
}
But in MvvmCross 6.4.2, MvxIosSetup does not have the base constructor that takes 2 arguments. Instead I have:
[Register(nameof(AppDelegate))]
public partial class AppDelegate : MvxApplicationDelegate<Setup, App>
{
}
public class Setup : MvxIosSetup<App>
{
}
How can I configure it so that I can code my views without storyboards?
EDIT
I created a very small sample app with 1 view model/controller, using MvvmCross 7. The ViewDidLoad method never gets called in my MainViewController. Can someone please tell me why? I put my code here:
https://github.com/sinight95/TestApp/tree/main/TestApp
Neither the Setup.cs nor AppDelegate.cs files have anything to do whether you are presenting a storyboard or not. It is usually all up to which Presentation Attributes you are applying to a ViewController.
However, there are some stuff set in the Info.plist that changes how MvvmCross expects things to be set up.
Now in your example App you've put up on GitHub, you can do the following to make it not use the storyboard:
Remove Main.storyboard
In Info.plist set Main interface to (none)
In Info.plist set launch images to LaunchScreen.storyboard
Remove the scene delegate stuff in Info.plist
Remove the constructor in MainViewController
What is the main issue here is that you are essentially telling iOS that the ViewController has a storyboard by supplying this constuctor:
public MainViewController() : base(nameof(MainViewController), null)
{
}
Also are telling iOS to use storyboards through the Info.plist with all the scene delegation stuff.
I've created a PR showing what needs to be changed to make it run without the storboard and show the blue background color you've set on your ViewController.
https://github.com/sinight95/TestApp/pull/1
I am getting what feels like a bug when trying to custom instantiate a window controller from a storyboard. I am using NSStoryboard.instantiateController(identifier:creator:), which is a new function as of MacOS 10.15. The block of code in question is:
let mainWC = storyboard.instantiateController(identifier: "id") { aDecoder in
MainWindowController(coder: aDecoder)
}
I have SUCCESSFULLY used basically this exact code for custom instantiating the main view controller, and just assigning that view to a new window and a new window controller. That works fine. I can also instantiate the window controller the old fashioned way without custom initialization with instantiateController(identifier:). But when I try the above code for custom instantiation of the window controller I end up with the following error:
Assertion failure in -[NSClassSwapper _createControllerForCreator:coder:]... Custom instantiated controller must call -[super initWithCoder:]
Note that both my custom view controller class (which works) and my custom window controller class MainWindowController (which doesn't work) have implemented the trivial initializer:
required init?(coder: NSCoder) {
super.init(coder: coder)
}
I know that this functionality is new as of OS 10.15, but the documentation says it should work for window controllers AND view controllers, and the error message does not make any sense to me.
I hit the same problem, I thought about it a bit and here is how I worked around it.
First, why do I need this for ? I wanted to inject some dependencies to my view controller hierarchy before it's built from the Storyboard. I guess that's what the API is intended to.
But then, would that method be working, how would I pass the injection information down the view controller hierarchy ?
So, as the method is working without bug for view controllers, I decided to inject the information directly at the root view controller.
So, I have in my storyboard :
A window controller scene named "my-window-controller", which window just points to an empty view controller.
A view controller scene named "root-view-controller", where all the view hierarchy is described.
And wherever I want to create that view controller, I just do :
func instanciateWindowController(storyboard: NSStoryboard) -> NSWindowController {
// Load the (empty) window controller scene
let wcSceneIdentifier = NSStoryboard.SceneIdentifier("my-window-controller")
let windowController = storyboard.instantiateController(withIdentifier: wcSceneIdentifier)
as! NSWindowController
// Load the root view controller using the creator trick to inject dependencies
let vcSceneIdentifier = NSStoryboard.SceneIdentifier("root-view-controller")
let viewController = storyboard.instantiateController(identifier: vcSceneIdentifier,
creator: { coder in
return MyOwnViewController.init(coder: coder,
text: "Victoire !") // just pass here your injection info
})
// Associate the window controller and the root view controller
windowController.contentViewController = viewController
return windowController
}
with
class MyOwnViewController: MSViewController {
init?(coder: NSCoder,
text: String) { // receive here the injection information
print(text) // use the injection information here
super.init(coder: coder)
}
// Not used, but required
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
This is filed as Feedback #FB7626059, if you’d like to pile on (I hit the issue too).
I recently started developing using Xamarin, so I'm by no means an expert and have been stuck on this problem for a day or so now.
First of all, I am not using storyboards. I am creating my own custom views (xib) and loading them from code
I'm building a new Xamarin.iOS app and am attempting to load a view controller from within another view controller. Initially, I am loading the first controller from the AppDelegate like so:
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
appStartUpController = new AppStartUpController();
window.RootViewController = appStartUpController;
window.MakeKeyAndVisible();
return true;
}
This loads my AppStartUpController fine which is basically just a loading screen with a background image and loading animation while I make an API call in the background. Once the API call has completed, I want to load another view controller.
After the API call has completed, I attempt to load the next Controller like so:
var controller = new CityPickerViewController();
this.NavigationController.PushViewController(controller, false);
And here is my CityPickerViewController:
public partial class CityPickerViewController : UIViewController
{
CityPicker_View v;
public CityPickerViewController(IntPtr handle) : base(handle)
{
}
public CityPickerViewController ()
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
v = CityPicker_View.Create();
this.View = v;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(false);
UIImage i = UIImage.FromFile("citypickbackground.jpg");
i = i.Scale(this.View.Frame.Size);
this.View.BackgroundColor = UIColor.FromPatternImage(i);
}
}
I'm probably missing something obvious here, but the CityPickerViewController will not load. If I put a break point within the code, the viewDidLoad / ViewWillAppear overrides never get hit.
I'm a rookie programmer and would definitely appreciate any tips on this. Thanks in advance!
Welcome to SO!
Try base.NavigationController.PushViewController(controller, true); instead since you don't have a local navigation controller.
There could also be an issue in your CityPickerViewController, so try a different ViewController if that doesn't work.
I'm trying to implement google sign in using this component for xamarin.ios: Google Sign-in for iOS
It works great on emulator but when it comes to actual device it's crashing once i tap signin button. (iOS 10.2 - emulator is also using same OS)
I have a custom button which calls SignInUser method on SignIn.SharedInstance
It's crashing with below error (only when the app is deployed on device)
Objective-C exception thrown. Name: NSInvalidArgumentException Reason: uiDelegate must either be a |UIViewController| or implement the |signIn:presentViewController:| and |signIn:dismissViewController:| methods from |GIDSignInUIDelegate|.
I'm calling function below to initialize GoogleSignIn on FinishedLaunching method of AppDelegate.cs
public void Configure()
{
NSError configureError;
Context.SharedInstance.Configure(out configureError);
if (configureError != null)
{
// If something went wrong, assign the clientID manually
Console.WriteLine("Error configuring the Google context: {0}", configureError);
SignIn.SharedInstance.ClientID = googleClientId;
}
SignIn.SharedInstance.Delegate = this;
SignIn.SharedInstance.UIDelegate = new GoogleSignInUIDelegate();
}
Here's my implementation of ISignInUIDelegate():
class GoogleSignInUIDelegate : SignInUIDelegate
{
public override void WillDispatch(SignIn signIn, NSError error)
{
}
public override void PresentViewController(SignIn signIn, UIViewController viewController)
{
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(viewController, true, null);
}
public override void DismissViewController(SignIn signIn, UIViewController viewController)
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
}
}
So the emulator seems to know the methods are implemented, but not the device. Any idea what i am doing wrong here?
After some debugging i found where the actual issue was.
Somehow, the UIDelegate i assigned during initialization was lost when i was calling my login method. So i moved the line below from my initialization step to login
SignIn.SharedInstance.UIDelegate = new GoogleSignInUIDelegate();
Here's how my login method looks like now:
public void Login()
{
SignIn.SharedInstance.UIDelegate = new GoogleSignInUIDelegate(); //moved this here from Configure
SignIn.SharedInstance.SignInUser();
}
This took care of the issue for me but i am still not sure why this is only an issue on the device and not the emulator. Any Ideas?
Add a PreserveAttribute to your GoogleSignInUIDelegate class to prevent the Linker from removing the methods that can not be determined via static analysis.
Add the following class to your project:
public sealed class PreserveAttribute : System.Attribute {
public bool AllMembers;
public bool Conditional;
}
Apply the class attribute:
[Preserve (AllMembers = true)]
class GoogleSignInUIDelegate : SignInUIDelegate
{
~~~~
}
Re: https://developer.xamarin.com/guides/ios/advanced_topics/linker/
Setting PresentingViewController helped me to resolve the issue.
SignIn.SharedInstance.PresentingViewController = this;
Have found such fix here:
https://github.com/googlesamples/google-signin-unity/issues/169#issuecomment-791305225
I have been using MvvmCross on a cross platform mobile project and have 2 different views in a MonoTouch project that are using the same shared viewmodel and not sure how to go about structuring my code to navigate to different views using the same viewmodel in MvvmCross.
The default convention used by the MvvmCross platform is to automatically register all views using reflection.
This is done in the base Setup class - in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Platform/MvxBaseSetup.cs:
protected virtual void InitializeViews()
{
var container = this.GetService<IMvxViewsContainer>();
foreach (var pair in GetViewModelViewLookup())
{
Add(container, pair.Key, pair.Value);
}
}
where GetViewModelViewLookup returns a dictionary of ViewModel type to View type:
protected virtual IDictionary<Type, Type> GetViewModelViewLookup(Assembly assembly, Type expectedInterfaceType)
{
var views = from type in assembly.GetTypes()
let viewModelType = GetViewModelTypeMappingIfPresent(type, expectedInterfaceType)
where viewModelType != null
select new { type, viewModelType };
return views.ToDictionary(x => x.viewModelType, x => x.type);
}
In universal iPad/iPhone apps you do occasionally want to include multiple views for each viewmodel - using one view in the iPad and one view in the iPhone.
To do this, there are now (literally just now!) some attributes available to mark your views as being "unconventional" - these are:
MvxUnconventionalViewAttribute
use this to mark that your view should never be included by convention
in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Views/Attributes/MvxUnconventionalViewAttribute.cs
MvxConditionalConventionalViewAttribute
an abstract attribute - override this to provide your own custom logic for inclusion/exclusion
in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Views/Attributes/MvxConditionalConventionalViewAttribute.cs
MvxFormFactorSpecificViewAttribute
iOS/Touch only
an attribute that will include the view if and only if the detected iPhone form factor matches the current device
in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Touch/Views/Attributes/MvxFormFactorSpecificViewAttribute.cs
The last of these is probably what you want in this case - you could implement simple iPhone/iPad switching for a MainViewModel using two views declared like:
[MvxFormFactorSpecificView(MvxTouchFormFactor.Phone)]
public class MyIPhoneView : BaseView<MainViewModel>
{
// iphone specific view ...
}
[MvxFormFactorSpecificView(MvxTouchFormFactor.Pad)]
public class MyIPadView : BaseView<MainViewModel>
{
// ipad specific view ...
}
Alternatively if you want a very custom configuration, you can override all 'convention-based' behaviour - you can implement your own override of GetViewModelViewLookup - e.g.:
protected override IDictionary<Type, Type> GetViewModelViewLookup(Assembly assembly, Type expectedInterfaceType)
{
if (IsIPad)
{
return new Dictionary<Type, Type>()
{
{ typeof(HomeViewModel), typeof(IPadHomeView) },
{ typeof(DetailViewModel), typeof(IPadDetailView) },
{ typeof(AboutViewModel), typeof(SharedAboutView) },
};
}
else
{
return new Dictionary<Type, Type>()
{
{ typeof(HomeViewModel), typeof(IPhoneHomeView) },
{ typeof(DetailViewModel), typeof(IPhoneDetailView) },
{ typeof(AboutViewModel), typeof(SharedAboutView) },
};
}
}
Note that eventually you may decide that you need additional ViewModels as well as Views for the iPad app - the iPad has, after all, a much bigger screen - in this case you can add them manually. Ultimately, when your app hits a few million users, you may even decide to completely branch the tablet code away from the phone code - but that can generally wait until you hit that few million mark...
Another way to do it is to go ahead and create 2 ViewModels, but have them both subclass an abstract ViewModel, as follows:
FirstViewViewModel : BaseViewModel
SecondViewViewModel : BaseViewModel
With the corresponding views named:
FirstView.xaml
SecondView.xaml
This way, you are able to place some shared behavior in BaseViewModel, while the 2 subclasses are really just there to satisfy MvvmCross' view fetching conventions.
I recently started with MvvmCross and I am using v4.2.1. It seems that some names have changed. I am using one ViewModel with seperate iPhone and iPad views with the following:
[MvxFormFactorSpecific(MvxIosFormFactor.Phone)]
public class MyIPhoneView : BaseView<MainViewModel>
{
// iphone specific view ...
}
[MvxFormFactorSpecific(MvxIosFormFactor.TallPhone)]
public class MyTallIPhoneView : BaseView<MainViewModel>
{
// tall iphone specific view ...
}
[MvxFormFactorSpecific(MvxIosFormFactor.Pad)]
public class MyIPadView : BaseView<MainViewModel>
{
// ipad specific view ...
}