Reusable NSView from .xib - cocoa

In several parts of my application, I need a control where a user can enter a password. The password field is secure, however I want the user to have the ability to switch the field to plain text and check for typos, etc.
The password field itself is no problem. I subclassed NSSecureTextField to get the behaviour I want. I make it bindable, so I can toggle the display from another control (say, a Checkbox).
Now, I want to reuse this in multiple places within my application. I can simply use my custom secure text field along side a checkbox everywhere I need this. But I would prefer to group these pieces together into a reusable component.
So I create one using interface builder:
Now, how can I use that in my different windows? I have added a 'Custom View' component to my window, and set it's type appropriately:
But at runtime, I just get an empty space.
1 - Presumably because my view is defined in a .xib - I need to do something in code to load my PasswordView instance from the .xib; but what exactly do I need to do? I'm having trouble working this out.
Specifically:
What is the process / API to load an instance of my view from a .xib? I'm struggling to find the documentation on this.
Once I have loaded an instance of my view, how to insert it in place of my Custom View 'placeholder'? Presumably I need an outlet to my custom view, but then what? Do I just assign my loaded instance to the outlet? Am I supposed to add it as a subview to the outlet?
Where should this logic be happening? Inside init, inside AwakeFromNib?
2 - I know I can use an NSViewController for my custom view (example), and load it that way. But do I really need a view controller just for this? It seems I should just be able to create standalone views and instantiate them easily... But perhaps this is not the Cocoa 'way'? It seems to me that there should be a straightforward way of composing a view from multiple .xibs without needing a controller for every sub view (which may just be a text field, or a button), but I'm just not finding it...
Thank you in advance.

If I am getting from your question - You have an XIB having window that contains a passwordTextField and a checkBox.
And you want this xib window to load everytime you need to show password field.
I would suggest you not to create a window, you can create an NSView with a passwordTextField and a checkBox.
And in all the windows draw an empty view and you can load this xib view onto that, so a single object can be used multiple times.
EDIT:
Here is some sample code how I use to load a labelView on main Window.
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize myLabelViewController;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
myLabelViewController=[[LabelView alloc] initWithNibName:#"LabelView" bundle:nil];
[_window setContentView:[myLabelViewController view]];
}
#end
LabelView.h
#import <Cocoa/Cocoa.h>
#interface LabelView : NSViewController
#end
LabelView.m
#import "LabelView.h"
#implementation LabelView
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
#end

Related

Use NSViewController with Custom View

I have a main .xib view with parts of it being made up of custom views. At the same time I have also created separate .xib subviews (together with their respective .h and .m files). These custom classes were then connected to the custom views in the main .xib
This setup works fine however I would like to have a number of NSViewController(s) control each of the different custom views. What is the recommended way to do this?
Eg. Main.xib > contains 'custom views' > each using an NSView custom class and designed in it's own .xib
An NSViewController class would respond to events occurring in one of these custom views instead of the NSViewController tied to Main.xib
I've ended up creating NSViewControllers with XIB files instead of the custom views I had previously.
I then created NSBox components for every custom view that I had. I connected each of these to the main NSViewController via IBOutlet(s).
Finally, I attached each custom view to the dedicated NSBoxes via the IBOutlets as follows:
- (void) awakeFromNib{
[super awakeFromNib];
//instantiate custom view controller
CustomViewController* customViewController = [[CustomViewController alloc] initWithNibName:#"CustomViewController"
bundle:nil];
[self.customNSBoxView setContentView:[customViewController view]];
}
What I've tended to do is just create a new NSViewController subclass and choose the 'Create XIB' option.
From there, I instantiate the view controller subclass and add it to the view hierarchy in code.
This doesn't completely do what you are suggesting, but it does keep things more modular / easier to test. The downside is that its challenging then to setup constraints between the parent and children. I've tended to do this manually as well, or add the subcontroller's view into a NSStackView which gives you some constraints.
There's probably a better way, but this is what we used in our multi-xib project.

How to use NSViewController in an NSDocument-based Cocoa app

I've got plenty of experience with iOS, but Cocoa has me a bit confused. I read through several Apple docs on Cocoa but there are still details that I could not find anywhere. It seems the documentation was written before the NSDocument-based Xcode template was updated to use NSViewController, so I am not clear on how exactly I should organize my application. The template creates a storyboard with an NSWindow, NSViewController.
My understanding is that I should probably subclass NSWindowController or NSWindow to have a reference to my model object, and set that in makeWindowControllers(). But if I'd like to make use of the NSViewController instead of just putting everything in the window, I would also need to access my model there somehow too. I notice there is something called a representedObject in my view controller which seems like it's meant to hold some model object (to then be cast), but it's always nil. How does this get set?
I'm finding it hard to properly formulate this question, but I guess what I'm asking is:how do I properly use NSViewController in my document-based application?
PS: I understand that NSWindowController is generally meant to managing multiple windows that act on one document, so presumably if I only need one window then I don't need an NSWindowController. However, requirements might change and having using NSWindowController may be better in the long run, right?
I haven't dived into storyboards but here is how it works:
If your app has to support 10.9 and lower create custom of subclass NSWindowController
Put code like this into NSDocument subclass
- (void)makeWindowControllers
{
CustomWindowController *controller = [[CustomWindowController alloc] init];
[self addWindowController:controller];
}
If your app has multiple windows than add them here or somewhere else (loaded on demand) but do not forget to add it to array of document windowscontroller (addWindowController:)
If you create them but you don't want to show all the windows then override
- (void)showWindows
{
[controller showWindow:nil]
}
You can anytime access you model in your window controller
- (CustomDocument *)document
{
return [self document];
}
Use bindings in your window controller (windowcontroller subclass + document in the keypath which is a property of window controller)
[self.textView bind:#"editable"
toObject:self withKeyPath:#"document.readOnly"
options:#{NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName}];
In contrast to iOS most of the views are on screen so you have to rely on patterns: Delegation, Notification, Events (responder chain) and of course MVC.
10.10 Yosemite Changes:
NSViewController starting from 10.10 is automatically added to responder chain (generally target of the action is unknown | NSApp sendAction:to:from:)
and all the delegates such as viewDidLoad... familiar from iOS are finally implemented. This means that I don't see big benefit of subclassing NSWindowCotroller anymore.
NSDocument subclass is mandatory and NSViewController is sufficient.
You can anytime access you data in your view controller
- (CustomDocument *)document
{
return (CustomDocument *)[[NSDocumentController sharedDocumentController] documentForWindow:[[self view] window]];
//doesn't work if you do template approach
//NSWindowController *controller = [[[self view] window] windowController];
//CustomDocument *document = [controller document];
}
If you do like this (conforming to KVC/KVO) you can do binding as written above.
Tips:
Correctly implement UNDO for your model objects in Document e.g. or shamefully call updateChangeCount:
[[self.undoManager prepareWithInvocationTarget:self] deleteRowsAtIndexes:insertedIndexes];
Do not put code related to views/windows into your Document
Split your app into multiple NSViewControllers e.g.
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:AAPLListWindowControllerShowAddItemViewControllerSegueIdentifier]) {
AAPLListViewController *listViewController = (AAPLListViewController *)self.window.contentViewController;
AAPLAddItemViewController *addItemViewController = segue.destinationController;
addItemViewController.delegate = listViewController;
}
}
Previous code is called on windowcontroller with viewcontroller as delegate (again possible only after 10.10)
I always prefer to use multiple XIBs rather than one giant storyboard/XIB. Use following subclass of NSViewController and always inherit from it:
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property(strong) IBOutlet NSView *viewToSubstitute;
#end
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)awakeFromNib
{
NSView *view = [self viewToSubstitute];
if (view) {
[self setViewToSubstitute:nil];
[[self view] setFrame:[view frame]];
[[self view] setAutoresizingMask:[view autoresizingMask]];
[[view superview] replaceSubview:view with:[self view]];
}
}
#end
Add a subclass of MyViewController to the project with XIB. Rename the XIB
Add NSViewController Object to the XIB and change its subclass name
Change the loading XIB name to name from step 1
Link view to substitute to the view you want to replace
Check example project Example Multi XIB project
Inspire yourself by shapeart or lister or TextEdit
And a real guide is to use Hopper and see how other apps are done.
PS: You can add your views/viewcontroller into responder chain manually.
PS2: If you are beginner don't over-architect. Be happy with the fact that your app works.
I'm relatively new to this myself but hopefully I can add a little insight.
You can use the view controllers much as you would in ios. You can set outlets and targets and such. For NSDocument-based apps you can use a view controller or the window controller but I think for most applications you'll end up using both with most of the logic being in the view controller. Put the logic wherever it makes the most sense. For example, if your nsdocument can have multiple window types then use the view controller for logic specific to each type and the window controller for logic that applies to all the types.
The representedObject property is primarily associated with Cocoa bindings. While I am beginning to become familiar with bindings I don't have enough background to go into detail here. But a search through the bindings programming guide might be helpful. In general bindings can take the place of a lot of data source code you would need to write on ios. When it works it's magical. When it doesn't work it's like debugging magic. It can be a challenge to see where things went wrong.
Let me add a simple copy-pastable sample for the short answer category;
In your NSDocument subclass, send self to the represented object of your view controller when you are called to makeWindowControllers:
- (void) makeWindowControllers
{
NSStoryboard* storyboard = [NSStoryboard storyboardWithName: #"My Story Board" bundle: nil];
NSWindowController* windowController = [storyboard instantiateControllerWithIdentifier: #"My Document Window Controller"];
MyViewController* myController = (id) windowController.contentViewController;
[self addWindowController: windowController];
myController.representedObject = self;
}
In you MyViewController subclass of NSViewController, overwrite setRepresentedObject to trap it's value, send it to super and then make a call to refresh your view:
- (void) setRepresentedObject: (id) representedObject
{
super.representedObject = representedObject;
[self myUpdateWindowUIFromContent];
}
Merci, bonsoir, you're done.

WebView - Failed to set (contentViewController) user defined inspected property on (NSWindow)

I create a new Xcode project from scratch and it compiles fine.
I add a label to the View Controller and this compiles / runs / shows fine.
I then drag in a WebView into the View Controller but get this message when I run the application:
Failed to set (contentViewController) user defined inspected property on (NSWindow): *** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (WebView)
What am I doing wrong?
Just Link Webkit.framework in general->Linked Framework and Libraries
Well first of all you need to define the Web view so it actually knows what todo all it is doing right now is trying to find what to run here, see a tutorial on youtube or such, this is the code i used for mine, just to set it up.
first you want to put the web view into the view controller and add a toolbar to the bottom of it.
add four bar button items onto the bar.
and put a fixed space in-between the two.
then you need to set the styles of them do this by going to attributes inspector> button type > choose style
choose the following for the button 1st one is rewind 2nd is forward 3rd one is refresh last one is Cancel.
then control click and drag the button to the web view it will give you the options for it like load rewind and refresh.
then go to your storyboard and make sure you have you main and header files connected do this by selected the view controller in the storyboard files, then
change the class to the view controller you want to hook up to.
then hit enter
go to your header file(.h) and insert the following code,
#property (nonatomic, strong) IBOutlet UIWebView *webView;
put it under the #interface with brackets but make sure that the code is not inside of the brackets.
then what you need to do is go to your main file(.m) and insert the following code like this
#interface WebViewController ()
#end
#implementation WebViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
//custom initialization
}
return self;
}
- (void)viewDidLoad {
NSURL *url = [NSURL URLWithString:#"https://www.google.com"];
NSURLRequest *requestURL = [NSURLRequest requestWithURL:url]; [_webView loadRequest:requestURL];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
accept obviously you need to replace the void or just past into the right places
Then build and Run your code use iOS simulator iPhone 6.
Hope this helps have a nice day!
You should be using the wrong property webFrame of WebView instead of mainFrame
if let url = NSBundle.mainBundle().URLForResource("index", withExtension: "html"){
editorWebView.mainFrame.loadRequest(NSURLRequest(URL: url))
}
I don't think you are doing anything wrong. Its just a bug

Click in view of NSCollectionViewItem

I'm new to Cocoa dev, so many concepts of it are not clear to me...
I'm trying to build a simple app which will use Flickr API to retrieve user photosets and show them in a NSCollectionView, by clicking them, will start to download the photos of the photo set.
I'm using Xcode 5.0.1 with latest SDK which is 10.9
After reading some articles about how to use binding to deal with NSCollectionView, I'm now facing another problem regarding handling events in NSCollectionViewItem.
Per I understanding, mouse events can be easily handled by implement
-(void) mouseDown:(NSEvent *)theEvent
In a NSView subclass, say
#interface MyViewController : NSView {
}
And assign the view custom class to the subclass I made (MyViewController) in InterfaceBuilder.
Now, I have no problem to do as above, and the mousedown did handled as expect in most of widgets.
The problem is, I have a NSCollectionViewItem subclass as below:
#interface MyItemController : NSCollectionViewItem {
}
I'm trying to implement mousedown method there, this class was set to as File's Owner in a separated nib file. And the view will be automatically load when the NSCollectionView loaded.
Now, MyItemController cannot be as customer class in the view object in IB which is obviously because of it is not a NSView subclass but a NSCollectionViewItem subclass.
If I write a subclass of NSView and make the custom class of view object, I can get the mousedown.
However, I cannot get the representedObject and index of NSMutableArray in this approach and they are the essential information I need.
So my question is, what is the right way to deal with mouse events view of NSCollectionViewItem?
My code in GitHub here:
https://github.com/jasonlu/flickerBackupTool
Thanks!
UPDATE
I found a approach to solve this problem is by subclassing NSView and implement mousedown and use super, subviews to get and index and the array itself
- (void)mouseDown:(NSEvent *)theEvent {
NSCollectionView *myCollectionView = (NSCollectionView *)[self superview];
NSInteger index = [[myCollectionView subviews] indexOfObject:self];
NSLog(#"collection view super view: %#",myCollectionView);
NSLog(#"collection index: %ld",index);
NSLog(#"array: %#", [[myCollectionView content] objectAtIndex:index]);
}
It seems work, but I'm not sue if this is the best practice, it looks like depends on view too much and took a long way to reach the array.
I wouldn't bet that NSCollectionView always creates all subviews (subviews which are far away from the viewing area might be delayed and/or reused). Therefore, I wouldn't rely upon subview searching.
Overload NSViewController to create an NSView so that the representedObject assigned to the NSViewController is accessible from the NSView. From there you could search the actual content for index determination.
Overloading NSCollectionView and recording the actual index during view creation would probably not work well because a deleted item probably doesNot re-create any views.

Consise simple example of working with NSCollectionView drag and drop

I would like to figure out how to perform drag and drop from strings in a table view onto a collection view. I know there are delegate methods for collectionView drag and drop but can't find any examples of how to implement them. I have my collection view set up, it seems to be working correctly but don't know how to finish.
Any help is appreciated.
Update: The collection view setup I am working with has 3 NSTextFields and 2 check boxes for each collection item. There is also a tableView in the same view. The table view is passed an a MutableArray of strings. I want to be able to drag string values from the table view rows into the appropriate textFields in the collection view item.
This is different from the typical way drag and drop is used for collection views.
I am going to answer my own question because I spent quite a bit of time today trying to figure this out, Many people struggle with this procedure and mostly because I feel bad I keep asking the Stack community all these collection view questions all week:
I discovered the default behavior of the NSTextField actually allows a drop if it is in focus. The problem is that I need the appropriate NSTextField to auto focus on mouse enetered event. So as things turned out, I did not even need the NSCollectionView drag and drop delegates. I needed the NSTableView drag delegates and I needed to subclass the NSTextField and implement mouse event (drop) delegates in it.
so my class for the original collectionViewItem look like this:
//someClass.h
#interface SomeClass : NSObject{
IBOutlet NSTextField *field1_;
IBOutlet NSTextField *field2_;
IBOutlet NSTextField *field3_;
IBOutlet NSButton *chkBox1_;
IBOutlet NSButton *chkBox2_;
}
#porperty(readwrite, copy) NSTextField *filed1_;
properties are made for all 5 outlets for binding purposes. If you follow the tutorial on the Mac OSX Dev Library ; Collection View Programming Guide, it walks you through the process of setting up a collection View but it uses bindings.
So now the key is to set up a textField sub class
//MyNSTextField.h
#import
#interface MyNSTextField : NSTextField{
//mouse positioning
unsigned long last_;
}
//MyNSTextField.m
#import "MtTextField.h"
#implementation
-(void)dealloc{
[super dealloc];
}
-(void)awakeFromNib{
//register for dragged types
//any other nib stuff
}
//three required mouse events
-(unsigned long)draggingEntered:(id<NSDraggingInfo>)sender{
//this forces the textfield to focus before drop
[self.window makeFirstResponder: self];
NSPasebord *pBoard;
self->last_ = DragOperationNone;
pBoard = [sender draggingPastboard];
return self->last_;
}
-(unsigned long)draggingUpdated:(id<NSDraggingInfo>)sender{
return self->last_;
}
-(void)draggingExited:sender{
if([sender draggingSource] != self){
self->last = NSDragOperationNone;
}
}
}// end class
now just go back to the original class and change the name of your textField outlets from NSTextField to MyNSTextField and in the collection view, select each textfield and assign it the new class name in the inspector and as long as you had your tableview drag delegates set up, or if your are dragging from some other source, make sure you have the appropriate dragging source delegates set and it should work.

Resources