I have implemented a subclassed version of NSTextField, which I've called CustomTextField, the code for which is below:
#interface CustomTextField : NSTextField
#property (nonatomic, strong) IBInspectable NSImage *backgroundImage;
#end
#implementation CustomTextField
- (void)awakeFromNib
{
[self setDrawsBackground:NO];
}
- (void)drawRect:(NSRect)rect
{
NSImage *backgroundImage = self.backgroundImage;
[backgroundImage drawInRect:rect fromRect:rect operation:NSCompositeSourceOver fraction:1.0];
[super drawRect:rect];
}
#end
I have three instances of this custom text field, which I've set-up in my XIB file. When I run the app, select a text field, type in some text, and hit 'Enter', I get the following output from Xcode:
malloc: protecting edges
malloc: enabling scribbling to detect mods to free blocks
malloc: nano zone does not support guard pages
malloc: purgeable zone does not support guard pages
My guess is that my subclass implementation is not handling something correctly, but I'm honestly not sure. Does anyone have some suggestions? Thanks!
You should call
[super awakeFromNib];
in your awakeFromNib method.
From the docs:
You must call the super implementation of awakeFromNib to give parent
classes the opportunity to perform any additional initialization they
require. Although the default implementation of this method does
nothing, many UIKit classes provide non-empty implementations. You may
call the super implementation at any point during your own
awakeFromNib method.
Related
Hy guys, I'm new at ObjC and I'm still learning;
[sliderContrast setHidden:YES] (i also used slider.hidden = YES) doesn't make the slider invisible, instead it works fine with textfields. Do you know why?
I've also tried using property and synthesize but the result doesn't change
---Interface
#interface Controller : NSWindowController{
IBOutlet NSTextField *labelContrast;
IBOutlet NSTextField *valueContrast;
IBOutlet NSSlider *sliderContrast;
}
- (IBAction)changeContrast:(id)sender;
#end
---Implementation
#import "Controller.h"
#interface Controller ()
#end
#implementation Controller
- (void)windowDidLoad {
[super windowDidLoad];
[labelContrast setHidden:YES];
[valueContrast setHidden:YES];
[sliderContrast setHidden:YES];
}
- (IBAction)changeContrast:(id)sender {
}
#end
If you declare pointers for your objects but you don't allocate them yourself you can not set anything that is not there. Your setHidden: method calls end up in local void aka nowhere.
programmatically
If you go the coding way you would declare, allocate and initiate first. With
labelContrast = [NSTextField alloc] initWithFrame:NSMakeRect(x,y,w,h)];
before you call other methods of the object class (Or similar init methods).After that you can call methods on the object.
Almost all objects inherit an according -(instancetype)init or -(instancetype)initWith... method you can use. If there is no init method given, then there is another way to do it right and the moment to start reading again :).
With Interface Builder
By typing IBOutlet or IBAction in front of a declaration you just give a hint for Xcodes Interface Builder where to hook up and apply onto (associate) the placed object in (nib,xib,storyboard) with its object ID in the XML scheme to a reference in code.
So after you connected your code and the object in IB you can avoid allocation and init process for that particular object. Be aware that calling methods on objects that are not instanced yet is not working. Which is why you code in - (void)windowDidLoad, -(void)viewDidLoad or -(void)awakeFromNib because those are the methods that get called after "IB" has done its job for you.
In a very simple test app, I have an NSViewController (strongly retained) in the appdelegate. I put this NSView inside the contentView of my NSWindow (which I have set to Release on Close in Interface Builder). But, when I exit the app, the NSView's dealloc method is never called. I would have expected it to be called by the following flow - NSWindow dealloc -> removes content view -> removes all subviews.
Also, TestViewController is not dealloced, unless I set the strong reference to it, to be nil in AppDelegate's applicationWouldTerminate method. Again, I would have expected it to be dealloced. But, it looks like AppDelegate is never dealloced.
I must be missing something basic in my understanding of Objective-C memory management. Could it be because on Mavericks Apple does a force quit of apps and hence there is no cleanup? I would appreciate being pointed in the right direction on this. Thanks
My Code
#import "AppDelegate.h"
#interface TestView : NSView
#end
#implementation TestView
- (void)dealloc { NSLog(#"TestView - Dealloc"); }
#end
#interface TestViewController : NSViewController
#end
#implementation TestViewController
- (void)loadView { self.view = [[TestView alloc] init]; }
- (void)dealloc { NSLog(#"TestViewController - dealloc"); }
#end
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow* window;
#property (strong) TestViewController* testViewController;
#end
#implementation AppDelegate
- (void)dealloc { NSLog(#"AppDelegate - dealloc"); }
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
// Insert code here to initialize your application
TestViewController* testViewController = [[TestViewController alloc] init];
self.testViewController = testViewController;
[self.window.contentView addSubview:testViewController.view];
}
- (void)applicationWillTerminate:(NSNotification*)aNotification
{
// Insert code here to tear down your application
// self.testViewController = nil;
}
#end
App termination doesn't bother cleaning up all of the individual objects. When the process terminates, the OS X kernel simply reclaims all of the resources used by the process. This is much faster.
From the Advanced Memory Management Programming Guide:
When an application terminates, objects may not be sent a dealloc message. Because the process’s memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods.
If you have stuff that really needs to be done just before the app terminates, either put it in the app delegate's -applicationWillTerminate: method or observe the NSApplicationWillTerminateNotification notification posted by the application object. Also, you need to not opt-in to sudden termination or, if your app is generally opted in to it, temporarily disable it for as long as it has something it really needs to do at termination.
I'm trying to find a solution that allows me to get keydown events in a view controller.
I do not believe a view controller is part of the responder chain by default.
I would appreciate a sample of how to go about this. I have had trouble finding documentation I can understand on how to add the VC to the responder chain and get the events.
Thanks.
Miek
You can implement something like this:
-(void) globalKeyDown: (NSNotification *) notification
method in your controller class, and then just add the observer in awakeFromNib...or loadView method of your controller
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(globalKeyDown:)
name:#"my_keyEvent"
object:nil];
}
in your view class
-(void)keyDown:(NSEvent *)theEvent
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"my_keyEvent"
object:theEvent
userInfo:#{#"sender":self}];
}
NSViewController doesn't have a default way to do this. However, you can achieve this through subclassing NSView. Here is the basic idea:
If you create a view subclass, you can set your view controller as a delegate and create a delegate method that handles events.
You can declare a delegate protocol at the start of your view header.
Import your view header in the view controller header. Declare the view controller as implementing the protocol.
In your view keyDown send the event to the delegate.
Another way is to post NSNotifications in your keyDown and observe and handle the notifications in your view controller. Other ways also exist.
NSView Subclass with Delegate method explained
Here is the delegation example with an NSView subclass which declares a protocol in its header with one required method, an IBOutlet id property that conforms to the protocol. The NSView subclass calls this method to its delegate whenever it wants to. If the delegate is nil, that's fine in Cocoa. Also note, tangentially, I have added IB_Designable and IBInspectable to the view's color properties. This allows setting them in IB and requires the 10.10 SDK.
The app delegate has imported the NSView subclass in the AppDelegate.m implementation file and adopted the protocol in the AppDelegate class extension at the top of the .m file. In the #implementation section it also implements the method.
Also note in IB, I added an NSView to the window, then set its class to the custom NSView subclass in the inspector. Finally, I set its eventDelegate IBOutlet to the AppDelegate proxy in IB.
Custom NSView subclass interface
#import <Cocoa/Cocoa.h>
#protocol EventDelegatingViewDelegate <NSObject>
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent;
#end
IB_DESIGNABLE
#interface EventDelegatingView : NSView
#property IBOutlet id<EventDelegatingViewDelegate> eventDelegate;
#property IBInspectable NSColor *fillColor;
#property IBInspectable NSColor *strokeColor;
#end
Custom NSView subclass implementation
#import "EventDelegatingView.h"
#implementation EventDelegatingView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {return YES;}
// The following two methods allow a view to accept key input events. (literally they say, YES, please send me those events if I'm the center of attention.)
- (BOOL)acceptsFirstResponder {return YES;}
- (BOOL)canBecomeKeyView {return YES;}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.fillColor set];
NSRectFill(self.bounds);
[self.strokeColor set];
NSFrameRect(self.bounds);
}
// Notice these don't do anything but call the eventDelegate. I could do whatever here, but I didn't.
// The NICE thing about delgation is, the originating object stays in control of it sends to its delegate.
// However, true to the meaning of the word 'delegate', once you pass something to the delegate, you have delegated some decision making power to that delegate object and no longer have any control (if you did, you might have a bad code smell in terms of the delegation design pattern.)
- (void)mouseDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
- (void)keyDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
#end
App Delegate (and eventDelegate!) implementation
#import "AppDelegate.h"
// Import the view class and if there were other files that implement any protocol
#import "EventDelegatingView.h"
// Declare protocol conformance (or more accurately, not only import that protocol interface, but say you're going to implement it so the compiler can nag you if you don't)
#interface AppDelegate ()<EventDelegatingViewDelegate>
#property (weak) IBOutlet NSWindow *window;
// For the simplest demo app we don't even need this property.
#property IBOutlet EventDelegatingView *eventDelegatingView;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
// It's all right here. Receive a reference to a view and a reference to an event, then do as you like with them.
#pragma mark - EventDelegatingViewDelegate
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent
{
NSString *interestingEventNote;
switch (anEvent.type) {
case NSKeyDown:
case NSKeyUp:
{
// For simplicity we won't try to figure out the modifier keys here.
interestingEventNote = [NSString stringWithFormat:#"%# key was pressed.", anEvent.charactersIgnoringModifiers];
}
break;
case NSLeftMouseDown:
{
interestingEventNote = [NSString stringWithFormat:#"Left mouse down at point %# in window", NSStringFromPoint(anEvent.locationInWindow)];
}
break;
default:
break;
}
NSLog(#"%# %# aView=%#\n note=%#", self, NSStringFromSelector(_cmd), aView, interestingEventNote?interestingEventNote:#"Nothing worth noting");
}
#end
And that's it for the power of delegation. Basically it's callbacks of sorts and is a great way to build a class to enable it to defer something elsewhere as wanted. Moving some business logic to the right place in a fairly lazy and open and loosely coupled way.
NOTE: My code example shows using the app delegate. But the principal is the same. A view controller is little more than a delegate and you can add as much or as little as you like.
In your NSWidow (or NSWindowController) class implementation set your view controller as the first responder:
[self makeFirstResponder:yourViewControllerInstance];
You must, of course, make your NSViewController class return YES to the acceptsFirstResponder message.
Absolutely beginner question. I am trying to make an app which will switch an array of images by swiping the screen sideways. So, as vanilla case of that I was trying to display an image first and then think about how to switch between images using an action. I am using XCode 4.2.
So, here's what I have so far. I have added a UIImageView to my storyboard, then Ctrl+dragged into the ".h" file to create an Outlet and it looks something like this:
SwitchViewController.h
#import <UIKit/UIKit.h>
#interface SwitchViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
And then in the ".m" file, I am trying to set an image to the UIImageView in the DidLoad method.
SwitchViewController.m
#import "SwitchViewController.h"
#implementation SwitchViewController
#synthesize imageView;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *plate1 = [UIImage imageNamed:#"plates1.tif"];
[imageView setImage:plate1];
}
- (void)viewDidUnload
{
[self setImageView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ((interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeLeft))
return YES;
return NO;
}
#end
Now, if I try to run this, there are no compilation errors. But the program halts saying, Thread 1:Program received signal "SIGABRT"
Referring to your actual problem, you're getting this exception being thrown:
2012-01-26 22:04:40.728 Switch-a-Switch[548:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<SwitchViewController 0x6840660> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key UIImageView.'
It would appear that you've wired up the UIImageView to a property called UIImageView rather than imageView. Go and redo the connection in Interface Builder and make sure you drag from the imageView property to your UIImageView instance.
UIImage *plate1 = [UIImage imageNamed:#"plates1.tif"];
[imageView setImage:plate1];
should be kosher in iOS and 64 Bit (non-fragile ABI environments, as the iVar will be synthesized), I think it is much better form to include the ivar in the interface....
#interface SwitchViewController : UIViewController
{
UIImageView *imageView;
}
The thing that you should do to track this down is to turn on zombies and enable an exception breakpoint.
afew things there. (now im only learning myself so may be wrong but here goes)
when you use the #property u should use the self.imageView setter to assign to it. so [imageView setImage:plate1] would become self.imageView.image = plate1. and change it from weak to retain in the .h. and in the viewdidunload do self.imageView = nil. also i think the swiping of images effect your trying to achieve here might be better suited to a horizontal scrollview. and u can turn on paging or something for that.
hope that helps and didnt send u completely wrong direction :P
I think you may be going about this the wrong way. If you want to side swip to go between images, you should use a UIScrollView with paging enabled. A quick google came up with this.
Hope that helps
When implementing the UITextFieldDelegate in my ViewController class, the following error is thrown when entering the first character in the text field:
-[MyViewController respondsToSelector:]: message sent to deallocated instance...
So, I tried creating a separate class (inheriting only NSObject) and implementing UITextFieldDelegate. Guess what, it worked perfectly. However, that introduces some other problems as I have to do a lot of ugly cross-class-communication that I'd like to avoid. Here's the relevant parts of my app delegate code:
#interface RMSAppDelegate : NSObject <UIApplicationDelegate,
UITabBarControllerDelegate>
#property (nonatomic, retain) UIViewController* myViewController;
#end
#implementation MyAppDelegate
#synthesize myViewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
myViewController = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self.window setRootViewController:myViewController];
[self.window makeKeyAndVisible];
return YES;
}
#end
.. and here's what is being displayed:
#interface MyViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) IBOutlet UITextField* pinTextField;
- (void)viewDidLoad;
#end
#implementation MyViewController
#synthesize pinTextField;
- (void)viewDidLoad {
// DOES NOT WORK (WHY?)
//[pinTextField setDelegate:self];
// WORKS, BUT I'D LIKE TO AVOID
[pinTextField setDelegate:[[[MyTextFieldDelegate alloc] init] autorelease];
[pinTextField becomeFirstResponder];
[super viewDidLoad];
}
#end
And please, if you see any code (even off topic) that I could be doing better, leave a comment.
Since you asked for off-topic code comments: You forget to call [super viewDidLoad]. You also don't need to redeclare the prototype in order to override it. And the #synthesize textFieldDelegate is not valid, as you have no property in the class named textFieldDelegate. And your dealloc method is releasing an ivar named tfd which doesn't seem to actually exist in the class.
Your real problem is that you are not properly retaining the view controller at whatever point you allocate it. It may be that the view controller is being instantiated in a nib and associated with an ivar rather than a property declared retain, or is not being associated with anything. Or it could be that you are allocating it in code, adding its view as a subview of something, and then releasing it without ever retaining the view controller itself. Or it could just be that you are just releasing it when you shouldn't.
Your other class works specifically because you are leaking the object, so it never gets deallocated. The better solution, were you to go with this method, would be to store the object in an ivar when you allocate it and then release it (and set the ivar to nil) in both dealloc and viewDidUnload.
Okay, I finally solved this on my own. I have not changed the code. My NIB (.xib) was the culprit!
I thought that nested UIViewControllers was OK, and I still think they are in some cases (and maybe using another programmatic method). Anyway, I was initializing my class MyViewController with a NIB that in the Objects panel had a UIViewController as the first object.
I solved this by having the UIView as the first object in the Objects panel, and setting the File's Owner to be the UIViewController instead.
Correct code, incorrect NIB. Thank you for your help.