I have installed lately XCode 4+, and since on I get warning message 'window' is deprecated.
I have subclassed NSView and called it CentralView and used it to load various views dynamically. I had to subclass the NSView, because there are methods I needed to override.
In other class that controls loading central views I have created following method:
- (IBAction)showUserInfoView:(id)sender{
NSLog(#"Load new user info page");
// Try to end editing
NSWindow *w = [centralView window]; // Here I get warning 'window' is deprecated
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
// Put the view in the box
NSView *v = [[viewControllers objectAtIndex:0] view];
NSArray* viewSet = [NSArray arrayWithObject: v];
[centralView setSubviews: viewSet];
}
As far as I am aware the window method in NSView is up to date. Why do I get message?
What type is centralView? What happens if you say [(NSView*)centralView window]?
It's possible that the compiler is not finding -[NSView window], but some other -window method which is deprecated. If centralView is declared as an id then the compiler doesn't know it's an NSView and has to guess.
Related
I'm new to Objective-C and feel it's important to learn. I gave myself the following challenge:
I have a NSSplitViewController setup with two classes for each view; (i) SourceViewController: NSViewController and (ii) DetailViewController: NSViewController.
By selecting a NSTableView row of the SourceViewController I want to change the image in the ImageView within the SourceViewController
I have an instance class method in DetailViewController that I'm trying to access from SourceViewController:
-(void) imageSelected: (NSString*) name{
self.imageView.image = [NSImage imageNamed:name];
}
To gain access, I have created a pointer to the DetailViewController class instance via the NSSplitViewController superclass. So I go up the tree and down again to the DetailViewController:
//gets the row number that was selected
-(void) tableViewSelectionDidChange:(NSNotification *)notification{
NSLog(#"%ld",[[notification object] selectedRow]);
NSInteger row = [[notification object] selectedRow];
//get access to the parent view controller
//NSSplitViewController *splitCV = self.parentViewController;
NSSplitViewController *splitCV = ((NSSplitViewController *)self.parentViewController);
NSViewController *imageVC = splitCV.childViewControllers[1];
NSLog(#"%#",imageVC.className); //detail view controller
//***** HERE'S THE PROBLEM******
imageVC.imageSelected(self.pictures[row]); //<- imageSelect isn't recognised
}
However, this doesn't work as autocomplete doesn't recognise the function pointed to by imageVC. printing the imageVC.className output, shows that the name is 'DetailViewController'.
What am I doing wrong and how can I fix this?
Thanks in advance
Can you try changing this:
NSViewController *imageVC = splitCV.childViewControllers[1];
into this:
DetailViewController *imageVC = splitCV.childViewControllers[1];
and see if imageVC.imageSelected(self.pictures[row]) now autocompletes correctly ?
How can I examine a list of all active objects that inherit UIViewController?
I'd like to know if an instance of MyViewController exists. Ideally I can get this information in a callback in UIApplicationDelegate (for example application:didReceiveRemoteNotification:).
I've tried logging something like navigationController.viewControllers w/ no luck. I've also tried topViewController and modalViewController properties on navigationController.
If you know for a fact that your rootViewController is a UINavigationController, you can iterate through the array of viewcontrollers and test it for a class type
BOOL success = NO;
NSArray *viewControllersArray = self.navigationController.viewControllers;
for (id vc in viewControllersArray)
{
if ([vc isKindOfClass:[MyViewController class]])
success = YES; // Found it!
}
Is there any kind of ID that can be used and set in the .nib/.xib via Xcode that can be queried at runtime to identify a particular view instance from code?
In particular when having multiple copies of the same NSView subclass in our interface how can we tell which one we're currently looking at?
In Interface Builder, there is a way to set the "identifier" of an NSView. In this case, I'll use the identifier "54321" as the identifier string.
NSView Conforms to the NSUserInterfaceItemIdentification Protocol, which is a unique identifier as an NSString. You could walk through the view hierarchy and find the NSView with that identifier.
So, to build on this post about getting the list of NSViews, Get ALL views and subview of NSWindow, you could then find the NSView with the identifier you want:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *viewToFind = [self viewWithIdentifier:#"54321"];
}
- (NSView *)viewWithIdentifier:(NSString *)identifier
{
NSArray *subviews = [self allSubviewsInView:self.window.contentView];
for (NSView *view in subviews) {
if ([view.identifier isEqualToString:identifier]) {
return view;
}
}
return nil;
}
- (NSMutableArray *)allSubviewsInView:(NSView *)parentView {
NSMutableArray *allSubviews = [[NSMutableArray alloc] initWithObjects: nil];
NSMutableArray *currentSubviews = [[NSMutableArray alloc] initWithObjects: parentView, nil];
NSMutableArray *newSubviews = [[NSMutableArray alloc] initWithObjects: parentView, nil];
while (newSubviews.count) {
[newSubviews removeAllObjects];
for (NSView *view in currentSubviews) {
for (NSView *subview in view.subviews) [newSubviews addObject:subview];
}
[currentSubviews removeAllObjects];
[currentSubviews addObjectsFromArray:newSubviews];
[allSubviews addObjectsFromArray:newSubviews];
}
for (NSView *view in allSubviews) {
NSLog(#"View: %#, tag: %ld, identifier: %#", view, view.tag, view.identifier);
}
return allSubviews;
}
Or, since you are using an NSView subclass, you could set the "tag" of each view at runtime. (Or, you could set the identifier at run-time.) The nice thing about tag, is that there is a pre-built function for finding a view with a specific tag.
// set the tag
NSInteger tagValue = 12345;
[self.myButton setTag:tagValue];
// find it
NSButton *myButton = [self.window.contentView viewWithTag:12345];
Generic NSView objects cannot have their tag property set in Interface Builder. The tag method on NSView is a read-only method and can only be implemented in subclasses of NSView. NSView does not implement a setTag: method.
I suspect the other answers are referring to instances of NSControl which defines a -setTag: method and has an Interface Builder field to allow you to set the tag.
What you can do with generic views is use user-defined runtime attributes. This allows you to pre-set the values of properties in your view object. So if your view defined a property like so:
#property (strong) NSNumber* viewID;
Then in the user-defined attributes section of the Identity inspector in Interface Builder, you could add a property with the keypath viewID, the type Number and the value 123.
In your view's -awakeFromNib method, you can then access the value of the property. You will find that in the example above, the viewID property of your view will have been pre-set to 123.
Here's how to simulate "tags" in OSX without subclassing.
In iOS:
{
// iOS:
// 1. You add a tag to a view and add it as a subView, as in:
UIView *masterView = ... // the superview
UIView *aView = ... // a subview
aView.tag = 13;
[masterView addSubview:aView];
// 2. Later, to retrieve the tagged view:
UIView *aView = [masterView viewWithTag:13];
// returns nil if there's no subview with that tag
}
The equivalent in OSX:
#import <objc/runtime.h> // for associated objects
{
// OSX:
// 1. Somewhere early, create an invariant memory address
static void const *tag13 = &tag13; // put at the top of the file
// 2. Attach an object to the view to which you'll be adding the subviews:
NSView *masterView = ... // the superview
NSView *aView = ... // a subview
[masterView addSubview:aView];
objc_setAssociatedObject(masterView, tag13, aView, OBJC_ASSOCIATION_ASSIGN);
// 3. Later, to retrieve the "tagged" view:
NSView *aView = objc_getAssociatedObject(masterView, tag13);
// returns nil if there's no subview with that associated object "tag"
}
Edit: The associated object "key" (declared as const void *key) needs to be invariant. I'm using an idea by Will Pragnell (https://stackoverflow.com/a/18548365/236415).
Search Stack Overflow for other schemes for making the key.
Here's a simple way get NSView tags in OSX without subclassing.
Although NSView's tag property is read-only, some objects
that inherit from NSView have a read/write tag property.
For example, NSControl and NSImageView have a r/w tag properties.
So, simply use NSControl instead of NSView, and disable (or ignore)
NSControl things.
- (void)tagDemo
{
NSView *myView1 = [NSView new];
myView1.tag = 1; // Error: "Assignment to readonly property"
// ---------
NSControl *myView2 = [NSControl new]; // inherits from NSView
myView2.tag = 2; // no error
myView2.enabled = NO; // consider
myView2.action = nil; // consider
// ---------
NSImageView *myView3 = [NSImageView new]; // inherits from NSControl
myView3.tag = 3; // no error
myView3.enabled = NO; // consider
myView3.action = nil; // consider
}
Later, if you use viewWithTag: to fetch the view, be sure to specify NSControl (or NSImageView) as the returned type.
Up front, let me just say that I know the code I'll be showing below is probably inefficient, but I'm doing this "the hard way" because that's usually how I get things to stick when I'm learning...
I've created a class that I'm calling at runtime and within that is a label (I'll be adding more labels dynamically, etc.) When I check the container that will hold these views, I first check to see if there are any other views within it already. If not, I add the view directly. If there are other views already in there, I need to check that it won't put two of the same item in there. Since these are based on a single class, my current line of madness is to use the "headerLabel" outlet I've set up in the xib for this class. So...
In PhysicalExamDetailsNoteItem.h:
#import <UIKit/UIKit.h>
#interface PhysicalExamDetailsNoteItem : UIViewController {
IBOutlet UILabel *headerLabel;
}
#property(nonatomic, retain)UILabel *headerLabel;
#end
In PhysicalExamDetailsNoteItem.m, I add: #synthesize headerLabel; after #implementation.
In ViewController.m:
#import "PhysicalExamDetailsNoteItem.h"
// ... And after a long list of code-based view setup, I call a method that includes the following ... //
if([[physExamDetailsNoteItems subviews] count] > 0)
{
NSLog(#"More than one item already exists in the container, check preexisting items for %# to eliminate duplication...", sender.currentTitle);
for (PhysicalExamDetailsNoteItem *subview in [physExamDetailsNoteItems subviews]) {
NSLog(#"subview.headerLabel.text = %#", subview.headerLabel.text);
if (subview.headerLabel.text != sender.currentTitle) {
NSLog(#"%# doesn't exist in the container, add it as a new item...", sender.currentTitle);
PhysicalExamDetailsNoteItem *noteItem;
noteItem = [[PhysicalExamDetailsNoteItem alloc] initWithNibName:#"PhysicalExamDetailsNoteItem" bundle:[NSBundle mainBundle]];
[physExamDetailsNoteItems addSubview:noteItem.view];
noteItem.headerLabel.text = sender.currentTitle;
}
}
}
else
{
NSLog(#"No items exist in the container, add a new item...");
PhysicalExamDetailsNoteItem *noteItem;
noteItem = [[PhysicalExamDetailsNoteItem alloc] initWithNibName:#"PhysicalExamDetailsNoteItem" bundle:[NSBundle mainBundle]];
[physExamDetailsNoteItems addSubview:noteItem.view];
noteItem.headerLabel.text = sender.currentTitle;
}
I'm getting the error at this section:
PhysicalExamDetailsNoteItem *subview in [physExamDetailsNoteItems subviews]) {
NSLog(#"subview.headerLabel.text = %#", subview.headerLabel.text);
I have the headerLabel outlet rigged from the File's Owner object to the label view within IB, and when the enclosing method runs the if condition for the first time, it uses the else clause and populates my container view and changes the label text just as I want it to, but once it is called again and looks for the headerLabel for a comparison, it bails and I get:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView headerLabel]: unrecognized selector sent to instance 0x4b17470'
Thanks in advance!
Because you are asking for all subviews, you are really getting all subviews. Clearly one or more of them are not of the class you are looking for. It's generally not safe to count on subviews of a given view to only be the views you have added yourself.
You should definitely change your fast enumeration declaration to use the UIView class as suggested by ennuikiller. But the fix to your crash will be to test that a given subview is of the expected class before calling your method on it, like so:
for (UIView * subview in [physExamDetailsNoteItems subviews]) {
if ([subview isKindOfClass:[PhysicalExamDetailsNoteItem class]]) {
PhysicalExamDetailsNoteItem * noteItem = (PhysicalExamDetailsNoteItem *)subview;
NSLog(#"noteItem.headerLabel.text = %#", noteItem.headerLabel.text);
}
}
This way, you'll use the dynamic runtime to ensure that the objects you're operating on are they type you expect.
I think your need to declare the fast enumeration like so:
for (UIView *subview in [physExamDetailsNoteItems subviews]) {
and not as
PhysicalExamDetailsNoteItem *subview in [physExamDetailsNoteItems subviews])
After all you are processing UIView, not UIViewControllers.
I am managing my own UINavigationBar. I need to do this due to extensive skinning. The documentation for UINavigationController warns that there are limitations to skinning the UINavigationBar when used with a UINavigationController.
I have put in extensive logging and from everything I can tell, pushing the "Back" button in the UINavigationController pops two items off of of the stack instead of one. I get a single delegate callback telling me that it is removing the logical item, but it actually removes that one and one more.
The item added to the UINavigationController in awakeFromNib should never be removed. It is being removed for some reason.
There are two similar questions, but neither have satisfactory answers. The two questions are:
UINavigationBar .items accessor doesn't return the current UINavigationItem
UINavigationBar seems to pop 2 items off stack on "back"
- (void)awakeFromNib {
[headerView setDelegate: self];
[headerView pushNavigationItem: tableDisplay animated: NO];
}
- (void) selectedStory: (NSNotification *)not {
[headerView pushNavigationItem: base animated: NO];
NSLog(#"Selected story: %#", base);
}
- (void) baseNav {
NSLog(#"Current items: %#", [headerView items]);
BaseInnerItem *current = (BaseInnerItem *)[headerView topItem];
[self addSubview: [current view]];
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPushItem: (UINavigationItem *)item {
return YES;
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPopItem: (UINavigationItem *)item {
return YES;
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item {
NSLog(#"didPushItem: %#", item);
[self baseNav];
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item {
NSLog(#"didPopItem: %#", item);
[self baseNav];
}
Edited to add relevant debugging from a single run:
2010-10-13 02:12:45.911 Remix2[17037:207] didPushItem: <TableDisplay: 0x5d41cc0>
2010-10-13 02:12:45.912 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>"
)
2010-10-13 02:12:49.020 Remix2[17037:207] didPushItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:49.021 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>",
"<WebDisplay: 0x591a590>"
)
2010-10-13 02:12:49.023 Remix2[17037:207] Selected story: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.498 Remix2[17037:207] didPopItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.499 Remix2[17037:207] Current items: (
)
You always have to call [super awakeFromNib] when your subclass implements that method, per the documentation for -awakeFromNib:
You must call the super implementation of awakeFromNib to give parent classes the opportunity to perform any additional initialization they require
Importantly, however, ...
I don't understand why you have to actually manage your own navigation bar. If you subclass UINavigationBar and only override certain drawing or layout methods such as -drawRect:, -layoutSubviews, etc., then all of the logic behind managing the navigation bar in a navigation controller will just fall back on the original UINaviationBar class.
I've had to do extensive view customization for almost every major UIKit class, but I always left the complicated logic to the original classes, overriding only drawing methods to customize the look and feel.
Incidentally, it's actually much easier to skin an entire app without subclassing at all if all you're doing is using custom image assets. By setting a layer's contents property, you can either customize the look and feel of a UIView-based class on an as-needed basis or throughout your entire app:
#import <QuartzCore/QuartzCore.h>
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage * navigationBarContents = [UIImage imageNamed:#"navigation-bar"];
self.navigationController.navigationBar.layer.contents =
(id)navigationBarContents.CGImage;
}
You can set the contents for any class that inherits from UIView: navigation bars, toolbars, buttons, etc. It's a lot easier to manage this way without having to subclass at all.
This appears to be a bug in the implementation of -[UINavigationBar items]
When called from inside the -navigationBar:didPopItem: delegate method, it will omit the last object. You can check this by calling [navigationBar valueForKey:#"_itemStack"] to retrieve the underlying array and see that the expected items are still there.
Adding a dispatch_async inside -navigationBar:didPopItem:method successfully works around the issue in my app.