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.
Related
I have an NSPopoverTouchBarItem in my Touch Bar, created in Interface Builder.
The popover has a custom NSView inside it, and want to load data in it only when the view is activated, but I can't find any way to recognize when the contained NSTouchBar or NSView becomes visible.
According to docs, NSTouchBarDelegate does not have any delegate methods for the view appearing, either.
Which class should I subclass, or should I be monitoring viewWillDraw on my custom NSView and set up some hacky scheme?
The docs were not too clear, but subclassing NSPopoverTouchBarItem gives you -(void)showPopover:(id)sender and -(void)dismissPopover:(id)sender.
You can then define a delegate method to tell the parent class that this popover did show.
-(void)showPopover:(id)sender {
[super showPopover:sender];
[self.delegate touchPopoverDidShow];
}
Hi I've seen this question asked a few times already but with no definite answer yet so I created it for xcode 7 and swift2 (which may have changed things a bit anyway).
I created a project using Xcode 7 and Cocoa OSX Story boards + swift2, so my project started with a NSWindowController that Connects to a NSViewController (as expected!). I added a NSToolbar to my window controller and added a NSButton to the toolbar. I changed my NSViewController to be one of the new NSSplitViewController that links to three NSViewControllers and displays their views horizontally - with vertical dividers - (similar to the layout you see in the photo app or pages in Yosemite +). My final goal will be that the button in My toolbar shows and hides the first split.
It is my understanding is, and I would expect that to achieve this I should create an action in the NSSplitViewController that changes the auto layout constrains more or less in the way they are working it out here: How to do collapse and expand view in mac application?.
And then somehow link this action to the NSButton that is in the Toolbar... which happens to be in the NSWindowController (far up and isolated in the hierarchy)...
I have already gone through other questions about NSToolbar and storyboards and failed to accomplish my goal:
The YouTube video: Cocoa Programming L17 - NSToolbar which is the closest I found to solve the problem, but his method does not work for storyboards, only creating your own xib file.
In this question: How to use NSToolBar in Xcode 6 and Storyboard? One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...). A second person suggested to create a view controller variable in the NSWindowController and manipulate its properties from there... but again, a bit dodgy too.
One latest comment I saw in that question which seems the best way to tackle the problem (but still not as good as I guess it could be) is to add a NSObjectController to the dock of each scene and when the scene loads, set the values of the objects to the other secene's controller. Is this really the best way to go ahead? If so, how could I achieve this one?
Apple did mention (again) in WWDC15 that they created storyboards for osx and the split-view controller that owns view-controllers so that you can move your logic and work to the specific view-controller, so I would be expecting to do everything from inside my split-view controller as this is the target that needs to change.
Does anyone know how to achieve this from the view controller itself? I really haven't been able to find a way to connect my ToolBarItem to it.
OK, I've created this question quite a few days ago and no answer so far so I've answer with what I recently did to overcome the problem.
After I created my Xcode project I did this:
Created a subclass MySplitViewController for the NSSplitViewController
Added an IBOutlet for each NSSplitViewItem. For example:
#IBOutlet weak var mySplitViewItem: NSSplitViewItem!
Created a subclass WindowController for the NSWindowController
Added an IBAction in the WindowController class that links to the NSToolbarItem (my button)
Added a property that gets the Window Controller's content as MySplitViewController
var mySplitViewController: MySplitViewController {
return self.window?.contentViewController as! MySplitViewController
}
Now I can access the split view controller's property from the Window Controller in the action I created:
mySplitViewController. mySplitViewItem.collapsed = true
I created some sample code that does this (but using a view controller and changing the text for a label here, just in case someone wants to see a working project with this behaviour. And a blog post about it too :)
One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...).
I think this first responder method is actually the proper way.
As an example:
Add something similar to the following, in whichever view controller makes sense.
#IBAction func doSomething(_ sender: AnyObject?) {
print("Do something.")
}
This will magically show up in the first responder:
In your storyboard, right-click the orange "first responder" icon above your window controller, and you should see doSomething in the very long list. You just need to connect that up to your toolbar button.
In the following screen capture, you can see my "Toggle Sidebar" button is connected to the toggleSidebar action in my first responder.
I didn't even have to write this method — it's provided by NSSplitViewController:
#IBAction open func toggleSidebar(_ sender: Any?)
So, I was working this same issue and finding no solution as you experienced. I read your post and was trying to figure how I would implement your solution when it occurred to me to use a notification. In about 30 seconds, I had a perfectly fine working solution:
In your windowController add an IBAction to post a notification like so
-(IBAction)toggleMasterViewClicked:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:nil];
}
Hook up that action to your NSToolbarItem, then in the viewController add self as an observer for that notification like so
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(toggleMasterView:) name:#"TestNotification" object:nil];
In your case, selector would be updateMyLabelText
I don't really see any downside here. No reference to other objects needed, no dependancies. Works flawlessly for me
While connectiong IBActions works by using either the First Responder or by adding an "Object" to the scene, then changing its class to the window's view controller class, this doesn't help with IBOutlets and delegates that you'd like to point to the view controller.
Here's a work-around for that:
Add the Toolbar to the View Controller, not to its Window. That way, you can make all the IBOutlet connections in the View Controller Scene easily. I've done that for years and found no issues with it, even when using Tabs.
You'll have to assign the window's toolbar in code, then. E.g. like this:
#interface ViewController ()
#property (weak) IBOutlet NSToolbar *toolbar; // connect this in your storyboard to the Toolbar that you moved to the View Controller Scene
#end
- (void)viewWillAppear {
[super viewWillAppear];
self.view.window.toolbar = self.toolbar;
}
I have an mainUIView within which there is a customised UIImageView (named myImageView).
In the myImageView.h file, I have declared:
#property (strong, nonatomic) mainViewController *viewController;
and in the myImageView.m file, I have the following codes:
self.viewController = self.parent;
This lines raises an error indication:
Property 'parent' not found on object of type myImageView.
I thought putting the myImageView inside the mainUIView would make the default parent-and-sub relationship, but obviously it's not so.
In the main storyboard, I tried to Ctrl-drag the myImageView to the mainViewController, but no options pops up for me to select delegate.
Can anyone give me explanation of these please? I'm feeling a bit confused... Many thanks!
As your myImageView is a UIView is has no parent. Is just has a superview which again is a UIView. Basically a view doesn't know its controller. Although the controller can implement a view's delegate to get informed about events. So your AppDelegate knows your ViewController and the controller knows your ImageView. The AppDelegates window knows the view of your controller which will most likely contain the whole view hierarchy (except AlertViews, HUDViews, modal views and simmilar...).
I think what you want to do works best implementing the delegate pattern. (see Apple Docs)
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.
I'm using an NSViewController class with a single view in it to display a progress indicator bar and some text fields. I'm trying to use progressIndicator setMaxValue:and theTextField setStringValue: but neither of these are doing anything.
I've done this before and I've checked and rechecked, it's fairly straightforward, the fact that it's not working makes me think that it has to do with the fact that the class is NSViewController. Which is why I tried
Timers *aTimer = [[Timers alloc] init];
[aTimer.timerNameLabel setStringValue:#"name"];
[aTimer.progressIndicator setMaxValue:x];
in the app delegate which is an NSObject class, but that didn't work either.
I've tried looking around the NSViewController documentation but I can't find anything that says it can't set those values so I don't know what's happening. What am I doing wrong?
You probably want to use -initWithNibName:bundle: instead of a regular init to initialize your custom nib.
EDIT: It seemed the problem was due to the view not being queried before getting other objects. By calling [myController view] you actually load the nib, which isn't done automatically when you initialize the view controller. So before you can use any element of the view, you need to call [myController view]