Consider the following architecture taken from PanoramaGL:
#protocol PLIView <NSObject>
#end
#interface PLView : PLViewBase
#end
#interface PLViewBase : UIView <PLIView, UIAccelerometerDelegate, PLTransitionDelegate>
#end
My current monotouch binding looks like this:
[Model]
[BaseType (typeof (NSObject))]
public partial interface PLIView {
}
[BaseType (typeof (UIView))]
public partial interface PLViewBase : PLIView/*, UIAccelerometerDelegate, PLTransitionDelegate*/ {
}
[BaseType (typeof (PLViewBase))]
public partial interface PLView {
}
Notice that my PLViewBase class would need to implement two delegate implementation to follow the original code. However, I do not need access to the function provided by these delegates.
Is it correct to just comment them out like I did? If not, how can I make a correct binding with 2 base types?
It is convenient for the C# classes to map to the underlying Objective-C classes because it will provide the same idioms, but technically, it is not necessary.
As long as the methods can be invoked in a class "Foo", you can list all of the methods that you are interested in there.
So for strange class hierarchies, you can flatten in the way that would make the most sense in C#, and define the methods that you want in the places that you need them.
Related
Do you guys know if it's possible to use a Swift nested class as UIViewController in Storyboard?
I'm looking into different ways to structure my projects, one of which is grouping related classes by nesting them in the same main class.
Let me explain with an example:
Say I have a screen for displaying my app's settings which needs a UIViewController and a model.
I could create 2 classes: SettingsController and SettingsModel, but I'm wondering if it wouldn't be nice to have it structured otherwise by having a main class Settings containing nested classes Controller and Model like so
class Settings {
class Controller: UIViewController {
...
}
class Model {
...
}
}
That way I could use them by doing Settings.Controller and Settings.Model which I think would look pretty nice.
Now this works fine expect when using storyboard. In Storyboard when I select a view controller to set the custom class, if I type in Setting.Controller and hit enter, the field does not validate and is cleared.
So my question is do you guys know what I'm doing wrong or if this is simply not possible?
Of course, it's after having posted the question that I found a solution so I'm sharing it for posterity.
So there was 1 problem to the way I was trying to do it:
Nested classes are references by using the dot
notation: Settings.Controller but Interface Builder does not
see that as a valid class name
The solution was simple, give the controller it's own Objc name:
class Settings {
#objc(SettingsController)
class Controller: UIViewController {
}
...
}
By doing this you give the nested controller an ObjC name thereby exposing it the Interface Builder. Now you can reference the controller by filling in SettingsController.
I don't think storyboards supports that (yet), so in the meantime you need to use some workarounds.
If all you want is the syntax Settings.Controller, you can do this: (Inspired by this answer)
Declare your Settings.Controller class not as a nested class, but as a standalone class SettingsController:
class SettingsController : UIViewController { ... }
Then in Settings, add a typealias:
class Settings {
typelias Controller = SettingsController
class Model { ... }
}
Now you can use SettingsController in the storyboard, but Settings.Controller in code.
I upgraded an old project to Xamarin.iOS Unified and MvvmCross 4.1.
When I run the app, I get the following exception:
MvvmCross.Platform.Exceptions.MvxException: View not loaded for MyView
My classes look like:
MyView : MvxViewController<MyViewModel> {}
MvxViewController<T> : UIViewController, IMvxBindingContextOwner, IUIWrappable
where T : ViewModelBase
ViewModelBase : MvxViewModel {}
I suspect I'm missing an interface or something on MyView to enable MvvmCross to operate correctly but I'm not sure what interface this might be.
I suspect this because I already had to hard code in mappings between the View and it's ViewModel, to enable MvvmCross to find the ViewModel through reflection/auto-discovery associated with MyView. That is, before this error, I was getting an unable to find associated ViewModel error.
If I make MyView implement IMvxIosView, this error goes away, and I then get a null reference on the ViewModel later on in my code, so somewhere that is not getting instantiated where it needs to. My impression was this should all be handled automatically by MvvmCross, but I might be wrong.
I'm looking at samples such as https://github.com/MvvmCross/MvvmCross/wiki/Tip-Calc-A-Xamarin.iOS-UI-project
where TipView contains
public new TipViewModel ViewModel {
get { return (TipViewModel) base.ViewModel; }
set { base.ViewModel = value; }
}
MyView's base has no such ViewModel property. What am I missing to have access to this?
TipCalc's sample code is still referencing the Cirrious namespace, so it's possible things have changed.
Any suggestions are appreciated. I'm new to Xamarin and MvvmCross (although experienced in Windows/iOS development), and I'm having trouble finding up to date documentation.
When creating MVVMCross ViewModels and Views,there are some requirements
1.ViewModel and View needs to have same name until "View"
eg: MyView , MyViewModel
2.You need to load stroryboard name to the Viewcontroller.
eg:namespace [MvxFromStoryboard("MyView")]
public partial class MyView:MvxViewController where TViewModel :MyViewModel
3. When loading View, you need to have default constructor.
public MyView(IntPtr handle) : base(handle){}
4.You need to bind viewmodel to the view.Most of the time in ViewDidLoad
method in ViewClass
var set = this.CreateBindingSet<MyView, MyViewModel >();
set.Apply();
Try doing this:
var vc = this.CreateViewControllerFor(MvxViewModelRequest.GetDefaultRequest(viewModelType);) as MvxViewController;
vc.OnViewCreate();
The vc.OnViewCreate(); should ensure your View is loaded.
And the line:
this.CreateViewControllerFor(MvxViewModelRequest.GetDefaultRequest(viewModelType);) as MvxViewController;
Should make sure that the ViewController is created with the ViewModel already set. That way you don't have to set it manually.
That method is an extension method defined in the MvxCanCreateIosViewExtensionMethods class https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/iOS/iOS/Views/MvxCanCreateIosViewExtensionMethods.cs
-- Edit --
Another thing I noticed in your sample code is this line:
MyView : MvxViewController<MyView> {}
The type parameter passed into MvxViewController<T> should be the associated ViewModel for MyView, not MyView itself. So for example, you might have something like:
MyViewModel : ViewModelBase {}
and then:
MyView : MvxViewController<MyViewModel> {}
-- UPDATE --
You should be inheriting from the existing MvxViewController provided with MvvmCross. https://github.com/MvvmCross/MvvmCross/blob/3c735adc534a5df2d4730e9d58a08f7863c30cee/MvvmCross/iOS/iOS/Views/MvxViewController.cs
Implementing IMvxIosView solved this particular error.
It looks like MvvmCross can't find the view unless it implements this interface.
Now I'm on to other errors.
I have two projects. The first one is the iOS-App which is referencing a class library with the target framework Xamarin.iOS. In that class library I implemented a CustomView which inherits from UIView. I also registered it with the "Register" attribute.
So in the interface designer I get the prefilled information to use this class as custom class after I dragged the UIView to my ViewController. But when I run the app I get the following information:
"Unknown class MyCustomView in Interface Builder file."
If I move the my CustomView from the class library project into my first project then it will run as expected.
Does someone know how to use a CustomView, which is located in a referenced class library, in an Interface Builder file?
I don't think it's possible to Register accross project boundaries. In my tests, even when you use the exact same namespace, you still end up with an error.
What is possible, on the other hand, is to use a proxy class and make use of inheritance to bring the desired behaviour to the proxy.
Basically, it boils down to this:
1) In your library, you define your Custom UIView and implement any behaviour you want to share:
public class MyCustomUIView : UIView
{
public MyCustomUIView(IntPtr ptr) : base (ptr)
{
}
public override void AwakeFromNib()
{
// Do something beautiful here...
AddSubview(new UILabel(new CGRect(0,0,200,30)) { Text = "I live in the library"});
BackgroundColor = UIColor.Green;
}
}
Note that we don't Registerthis class, because it won't work anyway.
2) Now in your app project, open up the storyboard in interface designer or XCode and assign a completely new name to your placeholder UIView. In my case, I chose MyCustomUIViewProxy. Xamarin studio will create two files for you: MyCustomUIViewProxy.cs and MyCustomUIViewProxy2.designer.cs. Note that in the designer.cs file, Xamarin automatically Registered your proxy class.
3) Now all we have to do is use inheritance, to make the proxy act like the original: In MyCustomUIViewProxy.cs, we change the base class from UIView to MyCustomUIView, and we're good to go:
partial class MyCustomUIViewProxy : MyCustomUIView
{
public MyCustomUIViewProxy (IntPtr handle) : base (handle)
{
}
}
That's it:
I have multiple controllers and apicontrollers in my project.
How to make both types of controllers inherit a base class, as there is more than some methods, that I need in both?
Composition might be a better option.
Basically instead of inheriting to gain the functionality stick it in a common class, that can be included as a property in both the Api and standard controller.
You can then inject the dependancy into both.
For example:
public class CommonControllerLogic : ICommonControllerLogic
{
public ActionResult SomeSortOfMethod()
{
// etc..
}
}
public class MobileApiController: ApiController
{
public ICommonControllerLogic CommonControllerLogic {get;set;}
// etc..
}
public class HomeController: Controller
{
public ICommonControllerLogic CommonControllerLogic {get;set;}
// etc..
}
Composition is often favoured over inheritence, there are loads of articles on it, just do a quick google search, have a read of this article.
MVC controllers inherit from Controller, whereas API controllers inherit from ApiController. C# does not support multiple inheritance easily. I would recommend moving the shared logic into a static class that both can call. Otherwise you could provide more detail about what is shared; in some cases other design patterns may be more appropriate.
public class MyController : Controller
{
private MyClass _class;
public MyController(MyClass class)
{
this._class = class;
}
}
public class MyClass
{
// stuff
}
My Ninject is hooked up to inject classes that implement IController (Controller class does so). But, I did not bind MyClass to anything, yet Ninject is still injecting MyClass into MyController.
I guess my question is, why does it inject something that I didn't bind to anything? Does Ninject run off an find the class with the signature MyClass? I assume this behavior would be different if my constructor required a MyBaseClass and I have two classes in my assembly that inherit from MyBaseClass?
In Ninject V1, ImplicitSelfBinding was a top-level config setting (which defaulted to true IIRC).
In V2, the implicit self binding behavior you observe is more deeply wired in (though there are ways of switching it off -- like most bits of Ninject, it's very granular and minimal). In V2, the default behavior is that self-bindings for concrete types are always generated if no other binding is present. The only time you typically do a Bind<Concrete>().ToSelf() is to customise the binding, e.g., to do a .InSingletonScope().
See this answer by #Remo Gloor for a way to turn it off in V2+.
Go do a grep in the source right now for ImplicitSelfBinding this instant - it's far easier to read than people rabbiting on though!
Also dont forget to have a look at Ninject.Extensions.Conventions and tests on ninject.org for arranging implicit Bind()ing of I*X* to *X*
(As Steven alluded to, Ninject would not self bind if you changed your MyClass class to be abstract.)