Using monotouch and monodevelop, I'd like to create a custom control.
I followed this steps:
add a new file as "iPhone View" (calling it TestView)
edit TestView.xib in Interface Builder, es. changing it's background and size
edit MainWindow.xib in Interface Builder, adding a UIView and setting it's class identity as "TestView".
At this point, I'd like to launch the application and see in the UIView of MainWindow the content of an instance of TestView.
In order to get this "binding" I tried several steps (I know it can be done via code creating the outlets, etc.., but I'd like to understand if it can be done via Interface builder).
In one of the methods tried, I set via Interface Builder the value "TestView" as class identifier in the View of TestView.xib: in this way MonoTouch created the code in TestView.xib.designer.cs.
[MonoTouch.Foundation.Register("TestView7")]
public partial class TestView7 {
}
Then, in TestView.xib.cs, I added:
public partial class TestView : UIView
{
public TestView (IntPtr p) : base(p)
{
}
}
When I launch the app, I cannot see in the view of MainWindow the content of TestView, but if in TestView constructor I add something such as
BackgroundColor = UIColor.Yellow;
then, I can see TestView in MainWindow... or better I can see only the yellow rectangle, not the real content!
So, the problem is that TestView is binded to the UIView in MainWindow, but it's content comes only from the code and not from the content defined via Interface Builder in TestView.xib.
Is there a way to load the content from the TestView.xib?
Take a look at http://ios.xamarin.com/Documentation/API_Design#Interface_Builder_Outlets_and_C.23
I believe what you need to do is add a constructor that handles the nib file (The following is from the above link):
When using MonoTouch your application will need to create a class that derives from UIViewController and implement it like this:
public class MyViewController : UIViewController {
public MyViewController (string nibName, NSBundle bundle) : base (nibName, bundle)
{
// You can have as many arguments as you want, but you need to call
// the base constructor with the provided nibName and bundle.
}
}
Then to load your ViewController from a NIB file, you do this:
var controller = new MyViewController ("HelloWorld", NSBundle.MainBundle, this);
This loads the user interface from the NIB.
The answer by Allison A in SO question monotouch - reusable iOS custom view, explains how to load existing composite views created in Interface Builder into a custom view.
Related
I am new to Xamarin and MVVMCross. So I have created 2 views. Login and Register. I have a button on Login to goto Register view and I am going there by this code in the Login's ViewModel:
// method when user tap register button
public IMvxCommand NavigateRegister
{
get { return new MvxCommand(() => ShowViewModel<RegisterViewModel>()); }
}
It works ok the Register Page opens well. But once I assign Name for a single object on Register view (a textEdit), the app crash when I tap on the Register button.
Below is the error msg:
Xamarin.iOS: Received unhandled ObjectiveC exception:
NSUnknownKeyException [
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key regNameEdit.
EDIT:
More details: I already assigned the name (see pic below), but still crash:
And the view also been assigned to its Class "CreateAccount". But I am noticing the class declaration has "partial" darkened out in the "public partial class CreateAccount : MvxViewController" line. That's the only noticeable difference btw this class and the first one.
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
namespace MyApp.iOS.Views
{
public partial class CreateAccount : MvxViewController
{
public CreateAccount() : base("CreateAccount", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
Title = "Register";
var set = this.CreateBindingSet<CreateAccount, Core.ViewModels.CreateAccountModel>();
set.Bind(regNameEdit).To(vm => vm.NameStr);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
The Bind(regNameEdit) also is an error (not detecting the textedit still)
This usually means that the a control is not defined in the View/ViewController class in your case the regNameEdit.
Make sure you created the back Property for this Edit and that the class assigned to the XIB is the one containing this property.
If you are using Xamarin Studio Designer you create the back property selecting the UIControl in the XIB/StoryBoard and setting a name, then enter.
This will create a property with the name you specified accessible in the ViewController.
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.MyUITextField.Text = string.Empty;
}
UPDATE
When using Storyboard:
Try this: Remove the name from the TextField and also remove the name from the ViewController class, then clean your project and rebuild. Re-add the class to the ViewController but when doing it click over the yellow icon in the bottom, there put the name and hit enter. Continue with the TextField, select it put the name and hit enter.
UPDATE # 2
When using XIBs
When you create the ViewController from the menu, Xamarin Studio will create both the ViewController class and the XIB file and will associate one with the other so here you don't have to do anything else to link them.
For the TextField you will need to do it adding the name as previously indicated.
Try this: Remove the name of the UITextField and save and clean/rebuild the project then add the name and hit enter.
Something you can do to verify if there's any problem, double click on the button in the XIB and this should take you to the ViewController class to create a method.
Hope this helps.
I have updated to mvvmcross 4. I am using storyboards and can't make the controllers be initiated by the viewmodel (pcl).
Does someone have a sample? I found a sample on mvvmcross page but they use xib, it wont work properly when using storyboards.
Any help would be great!
Have a look at the MvxFromStoryboard attribute, as shown here https://forums.xamarin.com/discussion/45253/cross-platform-development-with-mvvmcross
// Will look for a UIViewController with identifier "MyView" inside a Storyboard named "MyView.storyboard"
[MvxFromStoryboard]
public class MyView : MvxViewController
{
public MyView(IntPtr handle) : base(handle) {}
}
// Will look for a UIVIewController with identifier "MyOtherViewInSameStoryboard" inside a Storyboard named "MyView.storyboard"
[MvxFromStoryboard(StoryboardName = "MyView")]
public class MyOtherViewInSameStoryboard : MvxViewController
{
public MyOtherViewInSameStoryboard(IntPtr handle) : base(handle) {}
}
The ViewModel is connected to the ViewController via a naming Convention.
XYZViewModel ~> XYZViewController
I want to know whether it's possible for Xamarin Studio to set the type of an autogenerated outlet in, say, myview.designer.cs to the sublcass of a control within the .xib.
For example, I've subclassed NSButton to create some custom UI for buttons throughout the app.
I have my class defined as something like this, using the Register attribute so it's visible to the Objective-C runtime:
[Register("AppButton")]
public class AppButton : NSButton
{
...
}
Within XCode, I have then set the custom class to AppButton on my NSButtons. If I open the .xib in a text editor, I can see the customClass attribute on my buttons:
<button id="bxh-qr-g81" ... customClass="AppButton">
But when Xamarin Studio has listened for the changes, and updated the designer file, I always seem to get the outlet with a type of NSButton instead of AppButton.
[Register ("MyView")]
partial class MyView
{
[Outlet]
AppKit.NSButton MyButton { get; set; }
...
}
I would like it to be the type of my custom class:
[Register ("MyView")]
partial class MyView
{
[Outlet]
AppButton MyButton { get; set; }
...
}
The main reason for this is that I want to set some properties within AwakeFromNib, and it's a little tedious to cast it every time. It would also be nice for the compiler to throw an error if the type changed, rather than a runtime error, assuming I don't check that it really is the type of AppButton.
If I could set the properties directly on my subclass within XCode, this wouldn't be an issue. But as far as I can tell, IBInspectable or IBDesignable doesn't seem to be supported by Xamarin.
I got this to work by:
Creating the subclass like below
Building Xamarin studio first
opening xcode check for CustomButton.h and CustomButton.m file
Set the class type in xcode it should appear in the drop down:
Creating the outlets again. should then look like this:
[Outlet]
AppName.iOS.CustomButton customButton { get; set; }
I think you mean UIButton too not NSButton
using System;
using UIKit;
using Foundation;
namespace EdFringe.iOS
{
[Register ("CustomButton")]
public class CustomButton : UIButton
{
public CustomButton ()
{
}
public CustomButton (IntPtr p) : base(p)
{
//this one i think is used to create the obj-c object.
}
public CustomButton (NSCoder coder) : base(coder)
{
// This one i think is used when creating the xib.
}
//Custom stuff here
}
}
Am new to Mac apps, and am writing one simple app which has a common layout for different sections of the app. Its basically an image with one or two buttons(titles keep varying) in all sections.
So I thought of creating a CustomNSView with one Image Well and two rounded buttons in a new Nib file and in a separate class file (MyCustomView, which is a subclass of NSView) it would load this Nib in initWithframe method. So now when ever I drag drop a custom view and set its class to MyCustomView I get the Image and two buttons instantly without any additional code. But now how would I control these buttons(outlets/actionms) in other View Controllers ? The same view will be used every where so I cannot set files owner in nib to the view controller ?
Is it right in doing so ? Is there any way to create a custom view which would delegate all button actions to which ever view controller its included in ?
You can write your custom delegate. Though using that you can send messages from one object to another
Here's how I would do it. I wouldn't create a CustomNSView, I would create a CustomViewController (with its xib file included). On that CustomViewController, I would design the two buttons and setup CustomViewController.h like so.
#property (nonatomic, weak) id delegate; // Create a delegate to send call back actions
-(IBAction)buttonOneFromCustomVCClicked:(id)sender;
-(IBAction)buttonTwoFromCustomVCClicked:(id)sender;
CustomViewController.m like so.
-(void)buttonOneFromCustomVCClicked:(id)sender {
if ([self.delegate respondsToSelector:#selector(buttonOneFromCustomVCClicked:)]) {
[self.delegate buttonOneFromCustomVCClicked:sender];
}
}
-(void)buttonTwoFromCustomVCClicked:(id)sender {
if ([self.delegate respondsToSelector:#selector(buttonTwoFromCustomVCClicked:)]) {
[self.delegate buttonTwoFromCustomVCClicked:sender];
}
}
In interface builder of your customViewController, link up both button's SentAction event to the two methods (they should be displayed in file's owner).
Then in your otherClass in which you want to load the generic custom view, instantiate the generic view controller like so.
#import "customViewController.h"
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
customViewController *newCustomViewController = [[ViewController alloc] initWithNibName:#"customViewController" bundle:nil];
[newCustomViewController setDelegate:self];
self.backGroundView = [newCustomViewController view]; // Assuming **backGroundView** is an image view on your background that will display the newly instantiated view
}
-(void)buttonOneFromCustomVCClicked:(id)sender {
// Code for when button one is clicked
}
-(void)buttonTwoFromCustomVCClicked:(id)sender {
// Code for when button two is clicked
}
I am looking at rebuilding the settings section of my app using the new functionality provided by storyboards. Not wanting to touch the rest of my app at this point, so my main NIB will be staying.
Now when going from my NIB'ed tabBar to another NIB I just add a viewController to the tabBar in IB and then set the NIB Name property to the NIB which I want to load when that tab is pressed.
But there is no 'storyboard name' property that I can see, so how is this done?
There is no "official" way to do it at the moment, but you can do it using some tricks.
1) add your view controller to your tabbar in nib in the usual way. Leave the nib field empty.
2) create your storyboard and add your viewcontroller. Set the class and set a storyboard ID (I'll use "theID" for this example)
3) add a static bool var to your .m file, outside implementation or interface
static BOOL aFlag = NO;
4) in your viewcontroller class override this method:
- (id) awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (!aFlag){
aFlag = YES;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
return [storyboard instantiateViewControllerWithIdentifier:#"theID"];
} else {
return self;
}
}
essentially:
when you load the object from the tab bar nib, a first call to "initWithCoder" is made, and the object loads without nib
after initWithCoder, awakeAfterUsingCoder is called and there you substitute the object with another loaded from storyboard. Object of the same class but archived in the storyboard
when you load the object from the storyboard, another call to both initWithCoder and awakeAfterUsingCoder. You use the flag to avoid a loop and return self (at the second call, the object is loaded from storyboard so returning self is ok)
I tried and it works good ;-)
If you want here is an example project: http://www.lombax.it/files/testTabNib.zip