I have an NSSplitView showing two NSTableView instances. I need to detect which table view has become "active" (of focused), which means the one that the user has clicked. I need to know that because each table view acts as a source list for another view that shows the content of the selected row(s). This other view is shared for both tables.
I could do it by subclassing NSTableView and reacting to mouseDown: or another method but it I'd rather avoid subclassing just for that. I also don't want to track any NSWindow event just to know if the user has clicked one of the tables (I'd rather subclass NSTableView).
Currently, I use the delegate method tableViewSelectionDidChange:, but this method is, obviously, only called when the selected row changes. I need to know that a table becomes active even if the selected row hasn't changed.
Observing the clickedRow property of the table views doesn't appear to work. If may not be KVO compliant.
Any ideas?
For those interested, the most convenient solution I found was to take advantage of the fact that NSTableView is a subclass of NSControl. So just like NSButton it can send action messages when clicked (upon mouse up).
For each tableView, I wired its "action" to the same ibaction selector of my controller object in interface builder.
The controller identifies the sender and acts accordingly.
No need to subclass NSTableView.
Related
I'm literally going nuts.
I have a service app that opens a pretty simple NSPanel that consists of an NSTableView, a label, and three NSButton controls.
That's it.
The table view is view-based, and has four different NSTableCellView rows defined in IB.
The table code is pretty straight forward; my NSWindowController subclass is both the data source and the delegate. And for the most part, everything works perfectly:
the window opens
the expected data source methods (numberOfRowsInTableView: and
tableView:objectValueForTableColumn:row:) get called
the table gets populated with its views and section separators via
tableView:isGroupRow: and tableView:viewForTableColumn:row:
empty selections are disabled, so the delegate immediately receives a
tableView:shouldSelectRow: message, and the table automatically
selects the first row
the table appears exactly as it should and the first row is selected
Everything looks perfect, but I can't select a different rows. Nothing I click on in the table changes the selection.
I have tried:
subclassing NSTableCellView and verified that it is receiving a hitTest: call
tried "hacking" the table cell view hitTest: so it always return nil
tried various combination of "refuses first responder", "enabled", "editable" properties on the control view inside the table cell view
tried deleting all of the control views. so the table cell views were empty
tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:
changed the NSPanel into a regular NSWindow
Nothing seems to make any difference. Nothing I click on in the table changes the selection, and my table view delegate never receives another tableView:shouldSelectRow: call.
Note that all of the other (NSButton) controls in the window work just fine. I can click on any of them.
Update #1
Per the comment, I tried changing the product to a plain-old .app, but it makes no difference.
My next step was to fiddle some more with the delegate methods and have narrowed down the problem to this:
If I implement tableView:shouldSelectRow:, the table calls my delegate method twice (because I have have "empty selection" turned off, the table must initially determine which row select as a default, so I get two calls, one for row 0 (NO) and a second for row 1 (YES)). However, if I click in the table, I never receive another tableView:shouldSelectRow:.
If I remove the implementation of tableView:shouldSelectRow:, table selection magically starts to work. (Except for the fact that you can select group rows, which is worng.)
Also tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:; same behavior
So it appears if I implement any of the delegate methods to determine which rows are selectable, I can't select any rows. sigh
Update #2
I punted and refactored the app so there is now a new view controller object dedicated to managing the table view (rather than overloading the window controller). All data model and delegate methods were moved to the new view controller.
Also (thinking there might be something weird with the table view in IB), I deleted the table view from the NIB and recreated it and all of its connections.
Unfortunately, neither of these changes made any difference. If the tableView:shouldSelectRow: is implemented, the table is unusable. Delete the method, and it works again.
Update #3
And it just get weirder: thinking I could "hack" the row selection problem, I implemented the table view delegate methods tableViewSelectionIsChanging: and tableViewSelectionDidChange:. Neither get called. Even if I remove tableView:shouldSelectRow:, allowing the table selection to work, no tableViewSelectionIsChanging: or tableViewSelectionDidChange: is ever received.
Yet, if I add observers for NSTableViewSelectionIsChangingNotification and NSTableViewSelectionDidChangeNotification, those are received.
Also note that the NSViewController subclass that is the table view's delegate and data source explicitly conforms to <NSTableViewDelegate> and <NSTableViewDataSource>, so there's shouldn't be any reason why the table view should be confused as to what delegate methods are implemented.
Yikes! Now this is embarrassing.
So the problem was a weak reference(s) to the window controller.
Here's what was happening: the window controller was being created, loaded, and the window was presented. During the initial presentation and display, everything worked (tableView:shouldSelectRow: et. al.) because the window and view controllers existed. But in some future event loop, ARC destroyed the window and view controllers, leaving only the window on the screen with a table view, and the weak references to its delegate and data source objects were now nil.
Solution was to fix the window controller management so it keeps a strong reference to the window controller until the window closes.
Sometimes it's the simplest things that trip you up...
I have an NSTableView where I'd like to be able to highlight 1 or more rows, and then hit the delete key to delete them, or hit ⌘+C to copy them to the pasteboard.
I've found a suggestion to subclass the NSTableView and then code up a method for
-(void)copy:(id)sender
The documentation says that subclassing the NSTableView is rarely necessary. Instead, use the dataSource or delegate, or subclass a subcomponent. The delegate and dataSource protocols don't handle menu commands or keyboard short cuts. If I try to subclass anyways, I run into a problem where the subclass needs to send a message to the delegate, but my custom methods aren't part of the protocol, so the compiler complains. Rather than fight the design pattern, I'd rather know what the "correct" approach to this problem is.
I solved my problem by declaring the NSTableView's parent window's delegate as the NSTableView's controller (which is also its dataSource and delegate). Now it receives menu actions as part of the responder chain.
My Setup
I have a ViewController, where users type some sort of text in there. Call it InputViewController.
I have a UIView (in a separate class), where I draw a navigation bar that has a gradient (I override drawRect for Core Graphics, hence I need to to have in a separate class) which has a button. The button takes you to a MapViewController, which allows you to add a location tag to your input. Once a location tag is added, I would like to change the button's image.
My Problem
Once the button is tapped, I need to send the user's input to the MapViewController, where a delegate is also implemented and will pass back some information back to the InputViewController. The issue here is that the button resides in the separate UIView. One way to do this is pass the UIView the information once they are set, which in turn would be passed to the MapViewController once the button is tapped. The problem here would be the delegate as the delegate needs to return to the InputViewController and not the `UIView.
I was wondering if its possible to move the UIView into the InputViewController, including the drawRect method for that view. If so, how can I do that? If not, what are other ways/suggestions I can do to have the above set up?
I guess you need to use NSNotifications for sending messages to your classes. See this.
Assume you do not have a UIController to do the job. From inside the UIView .. how would you replace self with another UIView?
It's not very clear what you are trying to do from your question. UIController is not a class, for instance.
Ideally your app should be structured something like this:
UIViewController subclass
Controls a set of objects that are all on screen at one time. For example, any number of UITextFields, UIButtons, UIViews and UILabels.
has methods (IBActions and other delegate methods) which are triggered by user interaction with the controls and inputs.
has IBOutlets which allow it to manipulate what the user sees on screen. For instance an IBOutlet attached to a UILabel allows changing the text when a user presses a button.
UIView is only generally subclassed if you need custom drawing code, or some kind of custom control. Don't put application logic here if you can help it, and you can usually help it.
You can have multiple UIViewControllers but they usually function very independently. Often View Controllers don't maintain references for other view controllers. If they do it's loose couplings like the delegate pattern.
Bottom line: if you have two views controllers that need to communicate with each-other, you need to have a reference to one from the other. This usually occurs in the form of a property on one or both of the view controllers, and is connected either by interface builder or at run time when you create them.
You can add another subview using self.addSubview: you may also want to check self.bringSubviewToFront:
For more information, check the docs:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html
Here's a simplification:
I have an application with several buttons. If it is the first time the application is launching, I want to do some special things. In my AppController class, which is a delegate of NSApp, I use the delegate method -applicationDidFinishLaunching: to perform the test. After I've detected that it is the first time, I first want to access some IBOutlets. Then, I'd like to be able to get mouse events for each button, so that I can do other things.
I can't figure out want to do with the classes. I'd like to make a new class (FirstLaunch) for the first launch, but I'm not sure what to call from AppDelegate. Also, to get mouse events, shouldn't I be a sublass of the buttons, and considering that I have multiple buttons, I'm confused. I could probably tackle these issues one-by-one, but taken all together, they're confusing me.
Broken down, I need to access & manipulate IBOutlets I have set in IB, determine when buttons are clicked (and which button was clicked). I'd like to be able to do this from another class so as to not clutter up the AppDelegate.
Thanks for the help!
To be more clear, what I'm actually trying to do is to use Matt Gemmel's MAAttachedWindow to put up a help bubble by a button. When the button is clicked clicked, the bubble disappears and another one is put somewhere else. The bubbles will be attached to controls in the main window.
I'm guessing you want to show some additional user interface on the first launch? If it's a separate window, I'd advise creating a subclass of NSWindowController. Add a new NIB file for the first-run user interface to your project and change the class of the File's Owner object to FirstLaunch. Control-drag a wire from the File's Owner delegate onto the window to connect it with the window outlet.
You create IBOutlets by adding an instance variable to the class. If your app will only run on Leopard or higher, it's better to declare your outlets like this:
#interface FirstLaunch : NSWindowController {
NSTextField *myTextField;
}
#property (nonatomic, retain) IBOutlet NSTextField *myTextField;
#end
In Interface Builder, you'll control-drag a wire from the File's Owner onto the control to associate it with that outlet. Make sure that you release your reference to each IBOutlet in your class's dealloc method (if you're not using garbage collection) or else your app will leak memory.
Buttons send action messages when they're clicked, so you'll need to provide an action method for the button to call. You do that by declaring a method with a signature like this:
- (IBAction)myButtonClicked:(id)sender;
In Interface Builder, you'll control-drag a wire from the button onto your window controller and choose the myButtonClicked: method.
To make all this work, you'll need to create an instance of the window controller and tell it to load the NIB file at runtime. So, in your AppDelegate class, when you've determined that this is the first launch, you'll do this:
FirstLaunch *firstLaunchController = [[FirstLaunch alloc] initWithWindowNibName:#"nameOfNibFile"];
[firstLaunchController show:self];
You'll probably want to keep the reference to the window controller in an instance variable instead of a local variable like I've done here. And, depending on your application, it may make more sense to show this as a sheet. But once you've made it this far, you'll be able to figure out how to do that on your own.
Then, I'd like to be able to get mouse events for each button, so that I can do other things.
Don't worry about the mouse. There may not even be a mouse (think of the ever-popular tablet-Mac rumor).
I'd like to make a new class (FirstLaunch) for the first launch, but I'm not sure what to call from AppDelegate.
You make your own methods here. You'll probably make it a singleton*; then, you'll implement a method named something like runFirstLaunchPanel:, which will be an action method (more on those in a moment):
- (IBAction) runFirstLaunchPanel:(id)sender;
Instantiate the object in the nib, then, from your app delegate, call the action method with nil as the sender.
The reason to put the object in your nib and make the method an action method is that this makes it easy to hook up a menu item to it, so that the user can re-run the first-launch panel at a later time. (For example, if it's a Starting Points window, you might connect the New menu item to this action instead of the default one.)
*Yes, I've seen the articles about singletons, and I agree with them. In a case like this, it's OK.
Also, to get mouse events,
This is the wrong way of thinking about it. What you need to do is set your button up to send a message to your controller to make the controller (probably AppDelegate) do something. The message you want the button to send is an action message.
Implement an action method in the object that owns the nib containing the window with the buttons. Declare this method in the class's header, then connect the button to it in IB by right-clicking on your controller and dragging from the correct action method's circle to the button.
This is called the target-action paradigm, and it insulates controller responsibilities (doing things) from the views that ordered them. Because each action method does only one thing, you can have a button, a menu item, and even another controller (your app delegate, above) send the same action message, and the receiving controller won't have to care which control is sending the action, because it already knows what it has to do.
shouldn't I be a sublass of the buttons,
No. You very rarely create subclasses of anything other than NSObject (or, for model objects in Core Data, NSManagedObject) in Cocoa.
Note that I said “rarely”, not “never”. You will have to make the occasional subclass, especially if you want to create custom or customized views and cells (and, maybe, customized windows). However, subclassing is not necessary in Cocoa to the degree that (I hear) it is in some other frameworks on other platforms.
and considering that I have multiple buttons, I'm confused.
The target-action paradigm means you don't have to create one button subclass per button. One controller class implements all the actions, and the stock buttons, because you've hooked them up in IB, simply tell the controller “do this”.
Broken down, I need to access & manipulate IBOutlets I have set in IB,
Probably not. At least, not as much as you think you do.
determine when buttons are clicked (and which button was clicked).
Nope. The buttons will worry about being clicked; you just worry about setting them up to send, and then responding to, their action messages.