iOS: How do I know if a property is KVO-compliant? - objective-c

In the Key-Value Observing Programming Guide, the section Registering for Key-Value Observing says "Typically properties in Apple-supplied frameworks are only KVO-compliant if they are documented as such." But, I haven't found any properties in the documentation that are documented as KVO-compliant. Would you please point me to some?
Specifically, I would like to know if the #property(nonatomic,retain) UIViewController *rootViewController of UIWindow is KVO-compliant. The reason is that I'm adding the rootViewController property to UIWindow for iOS < 4 and want to know if I should make it KVO-compliant.
#interface UIWindow (Additions)
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#property (nonatomic, retain) UIViewController *rootViewController;
#endif;
#end
#implementation UIWindow (Additions)
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#dynamic rootViewController;
- (void)setRootViewController:(UIViewController *)newRootViewController {
if (newRootViewController != _rootViewController) {
// Remove old views before adding the new one.
for (UIView *subview in [self subviews]) {
[subview removeFromSuperview];
}
[_rootViewController release];
_rootViewController = newRootViewController;
[_rootViewController retain];
[self addSubview:_rootViewController.view];
}
}
#endif
#end

Short answer: No.
Long answer: Nothing in UIKit is guaranteed to be KVO-compliant. If you happen to find that KVO-ing a property works, be grateful, it's unintentional. Also: be wary. It could very well break in the future.
If you find that this is something you need, please file an enhancement request.
About your actual code, it's inherently flawed. Do NOT attempt to add a "rootViewController" setter to UIWindow this way. It will break when you compile your code on iOS 4 but someone runs it on an iOS 5 device. Because you compiled using the 4.x SDK, the #if statements will evaluate to true, meaning your category method smasher will be included in the binary. However, when you run it on an iOS 5 device, you're now going to get a method conflict because two methods on UIWindow will have the same method signature, and there's no guarantee as to which one will be used.
Don't screw with the frameworks like this. If you have to have this, use a subclass. THIS IS WHY SUBCLASSING EXISTS.
Your subclass would look something like this:
#interface CustomWindow : UIWindow
#property (nonatomic, retain) UIViewController *rootViewController;
#end
#implementation CustomWindow : UIWindow
static BOOL UIWindowHasRootViewController = NO;
#dynamic rootViewController;
- (void)_findRootViewControllerMethod {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
IMP uiwindowMethod = [UIWindow instanceMethodForSelector:#selector(setRootViewController:)];
IMP customWindowMethod = [CustomWindow instanceMethodForSelector:#selector(setRootViewController:)];
UIWindowHasRootViewController = (uiwindowMethod != NULL && uiwindowMethod != customWindowMethod);
});
}
- (UIViewController *)rootViewController {
[self _findRootViewControllerMethod];
if (UIWindowHasRootViewController) {
// this will be a compile error unless you forward declare the property
// i'll leave as an exercise to the reader ;)
return [super rootViewController];
}
// return the one here on your subclass
}
- (void)setRootViewController:(UIViewController *)rootViewController {
[self _findRootViewControllerMethod];
if (UIWindowHasRootViewController) {
// this will be a compile error unless you forward declare the property
// i'll leave as an exercise to the reader ;)
[super setRootViewController:rootViewController];
} else {
// set the one here on your subclass
}
}
Caveat Implementor: I typed this in a browser window

Based on #David DeLong's solution, this is what I came up with, and it works beautifully.
Basically, I made a category on UIWindow. And in +load, I (run-time) check whether [UIWindow instancesRespondToSelector:#selector(rootViewController)]. If not, I use class_addMethod() to dynamically add the getter & setter methods for rootViewController. Also, I use objc_getAssociatedObject and objc_setAssociatedObject to get & set the rootViewController as an instance variable of UIWindow.
// UIWindow+Additions.h
#interface UIWindow (Additions)
#end
// UIWindow+Additions.m
#import "UIWindow+Additions.h"
#include <objc/runtime.h>
#implementation UIWindow (Additions)
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
// Add rootViewController getter & setter.
static UIViewController *rootViewControllerKey;
UIViewController *rootViewController3(id self, SEL _cmd);
void setRootViewController3(id self, SEL _cmd, UIViewController *newRootViewController);
UIViewController *rootViewController3(id self, SEL _cmd) {
return (UIViewController *)objc_getAssociatedObject(self, &rootViewControllerKey);
}
void setRootViewController3(id self, SEL _cmd, UIViewController *newRootViewController) {
UIViewController *rootViewController = [self performSelector:#selector(rootViewController)];
if (newRootViewController != rootViewController) {
// Remove old views before adding the new one.
for (UIView *subview in [self subviews]) {
[subview removeFromSuperview];
}
objc_setAssociatedObject(self, &rootViewControllerKey, newRootViewController,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self addSubview:newRootViewController.view];
}
}
+ (void)load {
if (![UIWindow instancesRespondToSelector:#selector(rootViewController)]) {
class_addMethod([self class], #selector(rootViewController),
(IMP)rootViewController3, "##:");
class_addMethod([self class], #selector(setRootViewController:),
(IMP)setRootViewController3, "v#:#");
}
}
#endif
#end

Here's a solution using Associative References to define an instance variable with a category. But, it doesn't work cause, according to #Dave DeLong, I must use a run-time (not compile-time) check for this.
// UIWindow+Additions.h
#interface UIWindow (Addtions)
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#property (retain, nonatomic) UIViewController *rootViewController;
#endif
#end
// UIWindow+Additions.m
#import "UIWindow+Additions.h"
#include <objc/runtime.h>
#implementation UIWindow (Additions)
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#dynamic rootViewController;
static UIViewController *rootViewControllerKey;
- (UIViewController *)rootViewController {
return (UIViewController *)objc_getAssociatedObject(self, &rootViewControllerKey);
}
- (void)setRootViewController:(UIViewController *)newRootViewController {
UIViewController *rootViewController = self.rootViewController;
if (newRootViewController != rootViewController) {
// Remove old views before adding the new one.
for (UIView *subview in [self subviews]) {
[subview removeFromSuperview];
}
[rootViewController release];
objc_setAssociatedObject(self, &rootViewControllerKey, newRootViewController,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[rootViewController retain];
[self addSubview:rootViewController.view];
}
}
#endif
#end

Based on #David DeLong's feedback, I went with a simple subclass like so:
// UIWindow3.h
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#interface UIWindow3 : UIWindow {
}
#property (nonatomic, retain) UIViewController *rootViewController;
#end
#endif
// UIWindow3.m
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0
#import "UIWindow3.h"
#implementation UIWindow3
#synthesize rootViewController;
- (void)setRootViewController:(UIViewController *)newRootViewController {
if (newRootViewController != rootViewController) {
// Remove old views before adding the new one.
for (UIView *subview in [self subviews]) {
[subview removeFromSuperview];
}
[rootViewController release];
rootViewController = newRootViewController;
[rootViewController retain];
[self addSubview:rootViewController.view];
}
}
#end
#endif
However, this also required going through the existing code and using conditional compilation to cast UIWindow to UIWindow3 where ever rootViewController was being accessed. (Note: I think #David DeLong's solution may not require making these additional changes but rather just always using CustomWindow instead of UIWindow.) Thus, this is more annoying than if I could (only for iOS < 4) just add the rootViewController to UIWindow via a category. I may look into doing this with a category using Associative References (only for iOS < 4) because I think that looks like it'd be the most eloquent solution and might be a good technique to learn and have in the toolbox.

Related

Modifying string content in NSTextView works under viewDidLoad method, but not under myMethod

I am trying to update the contents of an NSTextView that is connected to myViewController as a referencing outlet to the Files Owner which is the subclass myViewController.
When I use an IBAction from a button, or use the viewDidLoad method of the controller, I can update the text fine. However, when I try run the method from another class (referred to in this example as anotherViewController), it runs the method, but the textview does not change.
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
self.outText.string = #"I am updated text! I also work!";
}
- (void)updateTextView:(NSString *)argText {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
- (void)updateTextViewWithoutArg {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
#end
In anotherViewController.m , which has all the relevant imports, I call this:
myViewController *viewtask = [[myViewController alloc] init];
[viewtask updateTextViewWithoutArg];
Nothing happens. The method runs and logs that it should have updated, but no text updates. I have tried many different approaches, including textstorage and scrollrange methods, they all work the already working sections, but make no difference in the sections not working.
I've also tried just for fun:
myViewController *viewtask;
[viewtask updateTextViewWithoutArg];
Also using the instance variable _outText
Also using [self.outText setString:#"string"];
Also using [_outText setString:#"string"];
Again, they work but only in the already working sections.
This should be simple but isn't logical to me. In swift all I need to do is
self.outText.string = "I update whenever I'm called!"
Views you create in Interface Builder are lazily created, so if you access them before viewDidLoad is called they are nil.
If your case, calling
myViewController *viewtask = [[myViewController alloc] init];
does not cause the views to be created so when you call
[viewtask updateTextViewWithoutArg];
self.outText is nil.
You can see that this is what is happening by updating your code as below:
- (void)updateTextView:(NSString *)argText {
NSAssert(self.outText != nil, #"self.outText must not be nil");
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
you should see the assert fire.
I appear to have found a solution by making myViewController a singleton class and using sharedInstance. For this particlar app, myViewController is a debug output window and will never need to be placed in another view.
I won't accept this answer yet, as it's not the best one I'm sure. There may still be a proper solution presented that allows finding the applicable myViewController instance, and modifying the outText property attached to it. Using this singleton makes subclassing tedious as I would have to make a new class for every instance if I wanted to be able to address say 10 View Controllers.
Anyway - the way I've been able to satisfy my simple requirement:
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
+ (id)sharedInstance;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
static myViewController *sharedInstance = nil;
+ (myViewController *)sharedInstance {
static myViewController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[myViewController alloc] init];
});
return sharedInstance;
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedInstance = self;
}
- (void)viewDidUnload {
sharedInstance = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
sharedInstance.outText.string = #"Button Pressed";
}
- (void)updateTextView:(NSString *)argText {
sharedInstance.outText.string = argText;
}
- (void)updateTextViewWithoutArg {
sharedInstance.outText.string = #"I make it to the TextView now";
}
#end
Now when I use this code from within anotherViewController.m it updates the right instance:
[myViewController.sharedInstance updateTextView:#"Updating with this string"];

NSTabView blocking file-drop events active on underlying Window.

I have successfully implemented a file-drop functionality in my app. The Application window has a few NSTabView objects where dropping on them does not work. Anywhere else in the window the file-drop works fine.
I have tried to make the app delegate a delegate for the NSTabView, but this did not help.
Anyone have a setup for the NSTabView not to filter out the drop-actions so the whole window can be transparent to the file-drop actions ?
For a more generic solution than olekeh's I made it IB friendly so you can hook it up to any object that complies with the NSDraggingDestination protocol.
#import <Cocoa/Cocoa.h>
#interface DropFilesView : NSView
#property (nullable, assign) IBOutlet id<NSDraggingDestination> dropDelegate;
#end
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender{
return [self.dropDelegate draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
return [self.dropDelegate performDragOperation:sender];
}
#end
I found the solution to this !! - I am posting it here for others who might need.
The NSTabView object has for each of its tabs an NSTabViwItem.
Under each of those, there is a regular NSView - that I subclassed with the following code: - The code assumes that you already have "draggingEntered" and "performDragOperation" in your AppDelegate as this class just forwards these messages to the app delegate. You will also need to put the declarations for those methods in you AppDelegate.h
// DropFilesView.h
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface DropFilesView : NSView
#end
and the implementation:
// DropFilesView.m
#import "DropFilesView.h"
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del performDragOperation:sender];
}
#end
In Interfacebuilder, I set the new class for all the NSView objects covering areas where drop does not work, to this new one.
A similar approach can be used for NSImageView and the WebView classes. However, for the last one, do not use [super awakeFromNib] to prevent the default drag-and drop handling for the web view object.

How to correctly implement a NSWindowController subclass with xib-file

I am trying to implement a NSWindowController subclass with new xib-file, I read up in lots of books, and researched on StackOverflow, but none of the steps provided made my window show, nor did the subclass code get executed. The new xib-file has its File's Owner set to "LogNavigatorController" and connections to the window and its contents have been made.
My AppDelegate.h:
#import <Cocoa/Cocoa.h>
#class LogNavigatorWindowController;
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
LogNavigatorWindowController *logsWindowController;
}
#end
My AppDelegate.m:
#import "AppDelegate.h"
#import "LogNavigatorWindowController.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
logsWindowController = [[LogNavigatorWindowController alloc] initWithWindowNibName:#"LogNavigatorWindowController"];
[logsWindowController showWindow:self];
}
#end
My LogNavigatorWindowController.h:
#import <Cocoa/Cocoa.h>
#interface LogNavigatorWindowController : NSWindowController
{
NSArray *directoryList1;
NSArray *directoryList2;
NSMutableArray *directoryList;
NSMutableArray *filePaths1;
NSMutableArray *filePaths2;
}
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTableView *logsTableView;
#property (unsafe_unretained) IBOutlet NSTextView *logsTextView;
#property (assign) IBOutlet NSArrayController *LogListController;
#property (retain) NSMutableArray *logsArray;
- (void) myDirectoryLogFunction;
#end
My LogNavigatorController.m:
#import "LogNavigatorWindowController.h"
#interface LogNavigatorWindowController ()
#end
#implementation LogNavigatorWindowController
#synthesize logsTableView;
#synthesize logsTextView;
#synthesize window;
- (id)init
{
self = [super initWithWindowNibName:#"LogNavigatorWindowController"];
[self loadWindow];
[self showWindow:#"Log Navigator"];
[self.window makeKeyAndOrderFront:nil];
if (self)
{
// Initialization code here.
[self myDirectoryLogFunction];
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
- (void) myDirectoryLogFunction
{
NSLog(#"Code execution test successful");
}
#end
You don't need to create the window property since it is already available for NSWindowController subclasses. Maybe that causes the problem.
Also your init method contains a lot of code that doesn't belong there. Remove
[self loadWindow];
[self showWindow:#"Log Navigator"];
[self.window makeKeyAndOrderFront:nil];
as well as replace
self = [super initWithWindowNibName:#"LogNavigatorWindowController"];
with
self = [super init];
You may want to remove the init method at all, since you don't need it in your case.
and move
[self myDirectoryLogFunction];
to the windowDidLoad method.
Also always check that the code for instantiating the window controller (in your case from the app delegates didFinishLaunching: ) is called. Sometimes it helps to create a new project and test there, if you may have changed too much within the original project and by accident removed delegate connections or similar.

Don't understand why I'm receiving EXC_BAD_ACCESS in xCode

I don't understand why I'm getting a EXC BAD ACCESS error when I call the mineHit method in my .m file. I understand that it's indicating that the button array has been released, but I don't understand why It would have been released at all.
#import "basicsViewController.h"
#implementation basicsViewController
#synthesize resetGame;
#synthesize scoreLabel;
#synthesize timeLabel;
#synthesize time;
#synthesize score;
-(void)newGame{
int index=0;
int yAxis=70;
for(int y=0;y<100;y=y+10){
int xAxis=20;
for( int x = 1; x < 11; x++) {
buttonArray[index] = [[UIButton alloc]init];
buttonArray[index] = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[buttonArray[index] setTag:index];
[buttonArray[index] addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
buttonArray[index].frame = CGRectMake(xAxis, yAxis, 26.0, 26.0);
NSLog(#"tag:%d xAxis:%d yAxis:%d",buttonArray[index].tag,(int)buttonArray[index].frame.origin.x,(int)buttonArray[index].frame.origin.y);
[self.view addSubview:buttonArray[index]];
xAxis=xAxis+28;
index=x+y;
}
yAxis=yAxis+28;
}
//generate bombs
for (int bombs=0;bombs<10;bombs++){
bombArray[bombs]= (arc4random()%99);
//TODO compare against bombArray to make sure of no duplicates
NSLog(#"BOMB AT %d",bombArray[bombs]);
}
}
- (IBAction)resetPress:(id)sender {
[self newGame];
}
- (void)buttonClicked:(UIButton*)button
{
BOOL hit;
NSLog(#"SELECTED BUTTON:%d",button.tag);
for (int b=0;b<10;b++){
if (button.tag==bombArray[b]){
//BOMB HIT
hit=YES;
b=10;
}
else {
//no bomb
hit=NO;
}
}
if (hit==YES){
//if hit
NSLog(#"HIT AT %d",button.tag);
[self mineHit];
}
else {
//if not hit
NSLog(#"%d is clean",button.tag);
[self cleanHit:button];
}
}
-(void)mineHit{
for (int d=0;d<100;d++){
NSLog(#"%i",buttonArray[d].tag);
buttonArray[d].enabled=NO;
[buttonArray[d] setTitle:#"*" forState:UIControlStateDisabled];
}
}
-(void)cleanHit:(UIButton*)button{
button.enabled=NO;
[button setTitle:#"!" forState:UIControlStateDisabled];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self newGame];
}
- (void)viewDidUnload
{
[self setResetGame:nil];
[self setScoreLabel:nil];
[self setTimeLabel:nil];
[super viewDidUnload];
}
#end
Here is my .h file
#import <UIKit/UIKit.h>
NSInteger bombArray[];
UIButton *buttonArray[];
#interface basicsViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *resetGame;
#property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property int time;
#property int score;
-(void)newGame;
-(void)buttonClicked:(UIButton*)button;
-(void)mineHit;
-(void)cleanHit:(UIButton*)button;
#end
When I compile your code I get four warnings. All four warnings are the same and say:
Tentative array definition assumed to have one element
The warnings apply to the definition of your bombArray and buttonArray arrays in the interface (.h) file.
If we give the two arrays a size your -mineHit method works fine.
Change the beginning of your .h file to:
#import <UIKit/UIKit.h>
NSInteger bombArray[10];
UIButton *buttonArray[100];
#interface basicsViewController : UIViewController
The compiler generates warnings for a reason and it is a good idea to try and get your code to compile cleanly with no warnings or errors.
Update: While we are here there is no reason why you couldn't move these arrays inside the interface and declare them as instance variables. Doing this would mean that the arrays are associated with an individual instance of the view controller. It is unlikely that you'd have multiple instances of this view controller but it is better to do it correctly now than get bitten later.
#import <UIKit/UIKit.h>
#interface basicsViewController : UIViewController {
NSInteger bombArray[10];
UIButton *buttonArray[100];
}
Interestingly, moving the declaration into the interface turns the warnings into errors.

changing UILabel text on a subview from main view

Ok, so I'm a relative noob with Objective-C/iOS programming, so hopefully someone with more knowledge here can help me out.
I have an iPad application using the SplitViewController template (with Core Data). I created another UIViewController (with xib file) called PlayerViewController. This View has several UILabel components on it.
I have a list of players that show up in the RootViewController (UITableView) and when you select a player, I programmatically create a PlayerViewController (in DetailViewController), pass it the NSManagedObject that was passed to the DetailViewController, try to set the text of one of the labels on the PlayerViewController's view, and then add it as a subview to the DetailViewController.
All of this works great except for the setting the text of the label on the PlayerViewController's view. I'm not sure what I'm doing wrong. I have used NSLog to confirm that the NSManagedObject is not nil and that the NSManagedObject property I'm trying to use has the correct text.
I'm at a loss here. Any help would be greatly appreciated. (Code follows):
This method is in the DetailViewController.m file:
- (void)configureView {
// Update the user interface for the detail item.
PlayerViewController *player = [[PlayerViewController alloc] init];
player.player = detailItem;
[self.view addSubview:player.view];
}
This method is called when the user selects an item from the RootViewController (This functionality, calling of configureView, is setup by the template and I haven't changed it).
Setting the player property of the PlayerViewController to object detailItem is handled in the setPlayer method of that class.
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
I then have a configureView method as well in PlayerViewController that sets the text of the label:
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
Ok, so the first NSLog statement prints the desired value, but the text of the UILabel (called nickName) returns nil.
The following is the full PlayerViewController.h & .m files:
PlayerViewController.h:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface PlayerViewController : UIViewController {
NSManagedObject *player;
IBOutlet UILabel *nickName;
IBOutlet UILabel *goalCount;
IBOutlet UILabel *assistCount;
IBOutlet UILabel *timeInGame;
}
#property (nonatomic, retain) IBOutlet UILabel *nickName;
#property (nonatomic, retain) IBOutlet UILabel *goalCount;
#property (nonatomic, retain) IBOutlet UILabel *assistCount;
#property (nonatomic, retain) IBOutlet UILabel *timeInGame;
#property (nonatomic, retain) NSManagedObject *player;
#end
PlayerViewController.m:
#import "PlayerViewController.h"
#implementation PlayerViewController
#synthesize nickName, goalCount, assistCount, timeInGame, player;
#pragma mark -
#pragma mark Managing the detail item
/*
When setting the player item, update the view
*/
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
/*
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
}
return self;
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
I'm sure I'm just missing something trivial, but I can't figure it out, and haven't been able to find any answers searching the web.
Thanks for any help!
Ok, so after playing with this for a bit and searching and searching around, I have gotten the answer to my problem. It turns out all the code I had was fine except the location of one statement. My call to configureView in PlayerViewController.m needed to be in viewDidLoad() not in the setPlayer() method. It all works great now.
Change the configureView method to that :
- (void)configureView {
nickName.text = (NSString*)[player valueForKey:#"Nickname"];
}
Yes, better place to call method is
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self configureView];
}
(void)setPlayer:(NSManagedObject *)managedObject called before your nib files loaded.