I have been trying to set a UIImageView background color (see below) in awakeFromNib
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
When it did not work, I realised that its probably because the view has not loaded yet and I should move the color change to viewDidLoad.
Can I just verify that I have this right?
gary
EDIT_002:
I have just started a fresh project to check this from a clean start. I setup the view the same as I always do. The results are that the controls are indeed set to (null) in the awakeFromNib. Here is what I have:
CODE:
#interface iPhone_TEST_AwakeFromNibViewController : UIViewController {
UILabel *myLabel;
UIImageView *myView;
}
#property(nonatomic, retain)IBOutlet UILabel *myLabel;
#property(nonatomic, retain)IBOutlet UIImageView *myView;
#end
.
#synthesize myLabel;
#synthesize myView;
-(void)awakeFromNib {
NSLog(#"awakeFromNib ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"AWAKE"];
[super awakeFromNib];
}
-(void)viewDidLoad {
NSLog(#"viewDidLoad ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"VIEW"];
[super viewDidLoad];
}
OUTPUT:
awakeFromNib ...
myLabel: (null)
myView : (null)
viewDidLoad ...
myLabel: UILabel
myLabel: UIImageView
I would be interested to know if this should work, from the docs it looks like it should, but given the way I usually set things up I can't quite understand why it does not in this case.
One more answer :-) It looks like you’re getting this behaviour because the controller loads the views lazily. The view is not loaded immediately, it gets loaded the first time somebody calls the view accessor. Therefore at the time you recieve awakeFromNib the NIB loading process is done, but not for the objects inside your views. See this code:
#property(retain) IBOutlet UILabel *foo;
#synthesize foo;
- (void) awakeFromNib
{
NSLog(#"#1: %i", !!foo);
[super awakeFromNib];
NSLog(#"#2: %i", !!foo);
}
- (void) viewDidLoad
{
NSLog(#"#3: %i", !!foo);
}
This logs:
#1: 0
#2: 0
#3: 1
But if you force-load the view:
- (void) awakeFromNib
{
NSLog(#"#1: %i", !!foo);
[super awakeFromNib];
[self view]; // forces view load
NSLog(#"#2: %i", !!foo);
}
The log changes into this:
#1: 0
#3: 1
#2: 1
I believe your call to super needs to be the first line in the awakeFromNib method, otherwise the elements won't be setup yet.
-(void)awakeFromNib {
[super awakeFromNib];
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
[testLabel setText:#"Pants ..."];
}
I know, this post is a bit older, but I recently had a similar problem and would like to share its solution with you.
Having subclassed NSTextView, I wanted to display the row colors in alternating orders. To be able to alter the colors from outside, I added two instance vars to my subclass, XNSStripedTableView:
#interface XNSStripedTableView : NSTableView {
NSColor *pColor; // primary color
NSColor *sColor; // secondary color
}
#property (nonatomic, assign) NSColor *pColor;
#property (nonatomic, assign) NSColor *sColor;
#end
Overwriting highlightSelectionInClipRect: does the trick to set the correct color for the respective clipRect.
- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
float rowHeight = [self rowHeight] + [self intercellSpacing].height;
NSRect visibleRect = [self visibleRect];
NSRect highlightRect;
highlightRect.origin = NSMakePoint(NSMinX(visibleRect), (int)(NSMinY(clipRect)/rowHeight)*rowHeight);
highlightRect.size = NSMakeSize(NSWidth(visibleRect), rowHeight - [self intercellSpacing].height);
while (NSMinY(highlightRect) < NSMaxY(clipRect)) {
NSRect clippedHighlightRect = NSIntersectionRect(highlightRect, clipRect);
int row = (int) ((NSMinY(highlightRect)+rowHeight/2.0)/rowHeight);
NSColor *rowColor = (0 == row % 2) ? sColor : pColor;
[rowColor set];
NSRectFill(clippedHighlightRect);
highlightRect.origin.y += rowHeight;
}
[super highlightSelectionInClipRect: clipRect];
}
The only problem now is, where to set the initial values for pColor and sColor? I tried awakeFromNib:, but this would cause the debugger to come up with an error. So I dug into the problem with NSLog: and found an easy but viable solution: setting the initial values in viewWillDraw:. As the objects are not created calling the method the first time, I had to check for nil.
- (void)viewWillDraw {
if ( pColor == nil )
pColor = [[NSColor colorWithSRGBRed:0.33 green:0.33 blue:0 alpha:1] retain];
if ( sColor == nil )
sColor = [[NSColor colorWithSRGBRed:0.66 green:0.66 blue:0 alpha:1] retain];
}
I do think this solution is quite nice :-) although one could reselect the names of pColor and sColor could be adjusted to be more "human readable".
Are you sure the objects are not nil? NSAssert or NSParameterAssert are your friends:
-(void) awakeFromNib {
NSParameterAssert(imageView);
NSParameterAssert(testLabel);
NSLog(#"awakeFromNib ...");
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
[testLabel setText:#"Pants ..."];
[super awakeFromNib];
}
If the objects are really initialized, try to log their address and make sure that the instances that appear in viewDidLoad are the same as those in awakeFromNib:
- (void) awakeFromNib {
NSLog(#"test label #1: %#", testLabel);
}
- (void) viewDidLoad {
NSLog(#"test label #2: %#", testLabel);
}
If the numbers are the same, you can create a category to set a breakpoint on setBackgroundColor and peek in the stack trace to see what’s going on:
#implementation UIImageView (Patch)
- (void) setBackgroundColor: (UIColor*) whatever {
NSLog(#"Set a breakpoint here.");
}
#end
You can do the same trick using a custom subclass:
#interface PeekingView : UIImageView {}
#end
#implementation PeekingView
- (void) setBackgroundColor: (UIColor*) whatever {
NSLog(#"Set a breakpoint here.");
[super setBackgroundColor:whatever];
}
#end
Now you’ll set your UIViewObject to be of class PeekingView in the Interface Builder and you’ll know when anybody tries to set the background. This should catch the case where somebody overwrites the background changes after you initialize the view in awakeFromNib.
But I presume that the problem will be much more simple, ie. imageView is most probably nil.
In case you're using a UIView subclass instead of a UIViewController subclass, you can override loadView method:
- (void)loadView
{
[super loadView];
//IBOutlets are not nil here.
}
Related
I made this play/pause/stop control but it doesn't work as expected. at load time it creates three views and stores them in an array, each view represents one state of the control. In the stopped state it contains a play button that is a member of a simple class cluster I made. In the other two states it contains a UIView with two of the buttons as subviews. In the first state it works and does exactly what it's supposed to, but when it tries to go to the next state it looks in the array and finds views at the playing state and paused state positions with no subviews. In fact, if you trace it through the execution of the loadView function the array never gets a view with subviews in it even though I called addSubview:(UIView *)view which the documentation says this about: This method retains view and sets its next responder to the receiver, which is its new superview.
I would really like some help trying to understand why this is happening. To be more clear, why don't the UIViews that are passed to the array have subviews when the local variables for them do.
Thanks in advance,
Rich
Here's the source:
// IMSpeechControl.h
#import <UIKit/UIKit.h>
#import "IMSpeechEngine.h"
typedef enum {
IMSpeechControlStateStopped = 0,
IMSpeechControlStatePlaying = 1,
IMSpeechControlStatePaused = 2
} IMSpeechControlState;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
#class IMSpeechControl;
#protocol IMSpeechControlDelegate <NSObject>
- (NSString *)speechControlNeedsText:(IMSpeechControl *)sender;
#end
#interface IMSpeechControl : UIViewController {
IMSpeechControlState controlState;
id delegate;
IMSpeechEngine *speechEngine;
NSMutableArray *controlButtons_;
CGRect frame_;
}
#property (nonatomic, readonly) IMSpeechControlState controlState;
#property (nonatomic, assign) id<IMSpeechControlDelegate> delegate;
// Designated initilazer
- (IMSpeechControl *)initWithFrame:(CGRect)frame;
// This must be here, do not call it from outside it's control buttons
- (void)changeToState:(IMSpeechControlState)controlState;
- (void)play;
- (void)pause;
- (void)stop;
#end
This is the important one.
// IMSpeechControl.m
#import "IMSpeechControl.h"
#import "IMSpeechControlButton.h"
#interface IMSpeechControl ()
#property (nonatomic, assign) IMSpeechEngine *speechEngine;
#property (nonatomic, retain) NSMutableArray *controlButtons;
// Used only for initialization, do not change after calling initWithFrame
// to change the view size after creation
#property (nonatomic) CGRect frame;
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation;
#end
#implementation IMSpeechControl
#synthesize controlState, delegate, speechEngine, frame=frame_;
#synthesize controlButtons=controlButtons_;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
- (IMSpeechControl *)initWithFrame:(CGRect)aFrame {
if (self = [super init]) {
self.frame = aFrame;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(speechEngineDidFinishSpeaking:) name:kDidFinishSpeakingNotificationName object:self.speechEngine];
}
return self;
}
- (void)loadView {
// Initialization code.
// Create the main view.
UIView *aView = [[UIView alloc] initWithFrame:self.frame];
self.view = aView;
[aView release];
// Create the sub-views and store them in an array.
NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:3];
// The stopped state play button view can be used directly since it is the only button shown.
IMSpeechControlButton *button = [[IMSpeechControlButton alloc] initWithFrame:self.frame forControl:self style:IMSpeechControlButtonStylePlay];
[controls insertObject:button atIndex:(NSUInteger)IMSpeechControlStateStopped];
[button release];
// The other two states require two buttons each, so the two buttons must be grouped into a UIView that can be easily switched out.
// Make two frames, one for the left and one for the right. Both are half the width of the main view
// The one on the left has the same origin as the main view...
CGRect halfFrameLeft = CGRectMake(frame_.origin.x, frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// and the one on the right starts half-way across the main view
CGRect halfFrameRight = CGRectMake((frame_.origin.x + (frame_.size.width / 2)), frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// Playing state
// Pause button
UIView *playingState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *plsPauseButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePause];
[playingState addSubview:plsPauseButton];
[plsPauseButton release];
// Stop button
IMSpeechControlButton *plsStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[playingState addSubview:plsStopButton];
[plsStopButton release];
[controls insertObject:playingState atIndex:(NSUInteger)IMSpeechControlStatePlaying];
[playingState release];
// Paused state
// Play button
UIView *pausedState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *pasPlayButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePlay];
[pausedState addSubview:pasPlayButton];
[pasPlayButton release];
// Stop button
IMSpeechControlButton *pasStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[pausedState addSubview:pasStopButton];
[pasStopButton release];
[controls insertObject:pausedState atIndex:(NSUInteger)IMSpeechControlStatePaused];
[pausedState release];
// store the array in an instance variable
self.controlButtons = controls;
[controls release];
// Set the view to it's first state (stopped)
IMSpeechControlButton *stoppedState = (IMSpeechControlButton *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
controlState = IMSpeechControlStateStopped;
}
- (IMSpeechEngine *)speechEngine {
if (nil == speechEngine) {
self.speechEngine = [IMSpeechEngine sharedManager];
}
return speechEngine;
}
- (void)changeToState:(IMSpeechControlState)state {
// This line caused my problem
// IMSpeechControlButton *currentView = [[self.view subviews] objectAtIndex:0];
// It should look like this
UIView *currentView = [[self.view subviews] objectAtIndex:0];
switch (state) {
case IMSpeechControlStateStopped:
{
UIView *stoppedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
stoppedState.alpha = 0.15;
}
completion:^(BOOL finished)
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{stoppedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStateStopped;
break;
}
case IMSpeechControlStatePlaying:
{
UIView *playingState = [self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePlaying];
[self.view addSubview:playingState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
playingState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{playingState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePlaying;
break;
}
case IMSpeechControlStatePaused:
{
UIView *pausedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePaused];
[self.view addSubview:pausedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
pausedState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{pausedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePaused;
break;
}
default:
NSLog(#"Error %lu is not a recognized IMSpeechControlState", state);
break;
}
}
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation {
// This notification is only sent if it has finished speaking and is therefore stopped.
[self changeToState:IMSpeechControlStateStopped];
}
- (void)play {
NSString *text = [delegate speechControlNeedsText:self];
[self.speechEngine speakText:text];
[self changeToState:IMSpeechControlStatePlaying];
}
- (void)pause {
[self.speechEngine pauseSpeaking];
[self changeToState:IMSpeechControlStatePaused];
}
- (void)stop {
[self.speechEngine stopSpeaking];
[self changeToState:IMSpeechControlStateStopped];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:kDidFinishSpeakingNotificationName];
[super dealloc];
}
#end
// IMSpeechControlButton.h
#import "IMSpeechControl.h"
typedef enum {
IMSpeechControlButtonStylePlay,
IMSpeechControlButtonStylePause,
IMSpeechControlButtonStyleStop
}IMSpeechControlButtonStyle;
#interface IMSpeechControlButton: UIView {
IMSpeechControl *speechControl;
UIImageView *imageView;
}
#property (nonatomic, assign) IMSpeechControl *speechControl;
#property (nonatomic, retain) UIImageView *imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style;
#end
// IMSpeechControlButton.m
#import "IMSpeechControlButton.h"
#import <Foundation/NSObjCRuntime.h>
#implementation IMSpeechControlButton
#synthesize speechControl;
#synthesize imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
{
NSString *str;
switch (style) {
case IMSpeechControlButtonStylePlay:
str = #"IMSpeechControlPlay";
break;
case IMSpeechControlButtonStylePause:
str = #"IMSpeechControlPause";
break;
case IMSpeechControlButtonStyleStop:
str = #"IMSpeechControlStop";
break;
default:
break;
}
isa = NSClassFromString(str);
// the speechControl must be set before calling subclass implementation of initWithFrame
self.speechControl = control;
return [self initWithFrame:aRect];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
All the control buttons have the exact same code as the play button, except that the handleGesture method calls the appropriate play/pause/stop function on the speechControl. The only reason I created all of them was so each of them could have their own images and so they can play different animations before changing state, but I didn't get to that yet.
// IMSpeechControlPlay.h
#import "IMSpeechControlButton.h"
#interface IMSpeechControlPlay : IMSpeechControlButton {
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
#end
// IMSpeechControlPlay.m
#import "IMSpeechControlPlay.h"
#implementation IMSpeechControlPlay
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
// TODO: set the image view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestureRecognizer.numberOfTapsRequired = 1;
gestureRecognizer.numberOfTouchesRequired = 1;
gestureRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[speechControl play];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
I found the problem, it was in this line:
IMSpeechControlButton *currentView = (IMSpeechControlButton *)[[self.view subviews] objectAtIndex:0]; in the changeState method.
I'd changed and moved this line several times during development, and had an older version of the file where it was correctly stated as:
UIView *currentView = [[self.view subviews] objectAtIndex:0];
but I just noticed that the file I was building has the first version. The version that I copied the source from turned out to be an old version that looked like this:
UIView *currentView = (IMSpeechControl *)[[self.view subviews] objectAtIndex:0];
Changing that to a UIView pointer makes it work. Looking at the debug info it looks like I was wrong about the subviews not getting retained, they were actually just unavailable in the debugger being cast away when the play control was pressed. Setting the speechControl variable before calling init actually works fine.
Thanks for all the advice and so quickly.
This method is just really wrong
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
Basically there is nothing to return here
self.speechControl = control;
return [self initWithFrame:aRect];
Because you haven't actually inited "self" yet. You need to rewrite that function to have a proper initializer like the one you have in IMSpeechControlPlay.
I created a custom AQGridViewCell.
By using UIImageView everything works. The image appears and is clickable, but when I change the UIImageView to TTImageView I can't click on the image.
The same example as here below by just changing the imageview to UIImageView and the the setter message to an image, everything works as expected.
Here is my ImageGridCell.h
#import "AQGridViewCell.h"
#import <Three20/Three20.h>
#interface ImageGridCell : AQGridViewCell
{
TTImageView * _imageView;
NSString *urlPath;
}
#property (nonatomic, retain) NSString *urlPath;
#end
And here is my ImageGridCell.m
#import "ImageGridCell.h"
#implementation ImageGridCell
#synthesize urlPath;
- (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) aReuseIdentifier
{
self = [super initWithFrame: frame reuseIdentifier: aReuseIdentifier];
if ( self == nil )
return ( nil );
_imageView = [[TTImageView alloc] initWithFrame: CGRectMake(0, 0, 100, 100)];
[_imageView setContentMode:UIViewContentModeScaleAspectFit];
[self.contentView addSubview: _imageView];
return ( self );
}
- (void) dealloc
{
[_imageView release];
[super dealloc];
}
- (CALayer *) glowSelectionLayer
{
return ( _imageView.layer );
}
- (UIImage *) image
{
return ( _imageView.image );
}
-(void)setUrlPath:(NSString *)urlpath {
_imageView.urlPath = urlpath;
}
I faced the same situation, and here is the solution I came with.
My AQGridViewCell have both a TTImageView and a UIImageView.
I use the TTImageView to load the image, and when it's loaded, I pass it to the UIImageView which displays it.
To do that, my AQGridViewCell subclass implements the TTImageViewDelegate protocol. In my PostGridViewCell.h, I have :
#interface PostGridViewCell : AQGridViewCell <TTImageViewDelegate> {
#private
TTImageView *ttImageView_;
UIImageView *imageView_;
}
// #property here …
#end
In my implementation file, in the initWithFrame:reuseIdentifier: method, I init both my ttImageView and my imageView, but I only add the imageView as a subview of my cell. And I set the ttImageView delegate property to self (so I can be notified when the image is loaded) :
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)aReuseIdentifier {
if ((self = [super initWithFrame:frame reuseIdentifier:aReuseIdentifier])) {
// init image
ttImageView_ = [[TTImageView alloc] initWithFrame:CGRectZero];
ttImageView_.delegate = self;
imageView_ = [[UIImageView alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:imageView_];
// …
}
return self;
}
When the TTImaveView has loaded the image, it calls the imageView:didLoadImage: method on the delegate (which is the PostGridViewCell). In my implementation file, I have this method :
- (void)imageView:(TTImageView*)imageView didLoadImage:(UIImage*)image {
self.imageView.image = image;
[self setNeedsLayout];
}
Does it help?
Cheers,
Thomas.
Not quite sure what you mean by "clickable" - like a UIButton? What behavior do you want to accomplish when a user taps one of the images?
I can't see the immediate problem here, but I can give you information that may help.
Foremost, TTImageView and UIImageView both descend from UIView, TTImageView does not descend from UIImageView (important to note):
UIImageView : UIView
TTImageView : TTView : UIView
We don't know what is going on behind the scenes in UIImageView, so I can't see what is different there, but the documentation seems to make clear that the UIImageView's default behavior for userInteractionEnabled is NO - which is odd, because you are saying that it is working with UIImageView, not the other way around.
Have you tried setting the userInteractionEnabled property to YES on TTImageView?
The problem here is that TTImageView has userInteractionEnabled to YES. So the touch event is being held by the TTImageView, and AQGridView expects touches in cell Views. Simply add userInteractionEnabled=NO to the TTImageView in your cell and you are ready to go.
I'm re-factoring my code and would like to move a whole bunch of UILabels into another class to tidy things up a bit. I'm missing one puzzle piece to be able to do so though (or maybe I'm just tired lol) Anyway here's the simplified code showing my issue. Thanks in advance to anyone who helps :)
#interface MyClass : UIView {
UILabel *classLabel;
}
#property (assign) UILabel *classLabel;
#end
#implementation MyClass
#synthesize classLabel;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
}
return self;
}
- (void)dealloc {[super dealloc];}
#end
#interface LabelTestViewController : UIViewController {
MyClass *myClassInstance;
UILabel *myLabel;
}
#property (assign) UILabel *myLabel;
#end
#implementation LabelTestViewController
#synthesize myLabel;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// this shows a label on the screen as expected
myLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 50, 20)];
myLabel.text = #"Hello";
[self.view addSubview:myLabel];
[myLabel release];
// this doesn't show anything on the scree
myClassInstance = [MyClass new];
[myClassInstance drawRect:CGRectMake(10, 50, 50, 20)]; // I suspect I need to call a different method, just don't know which one. initWithFrame is what I used at the time of creation of the label in the previous working scenario. is there an equivalent?
myClassInstance.classLabel.text = #"Goodbye";
[self.view addSubview:myClassInstance.classLabel];
}
- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];}
- (void)viewDidUnload {}
- (void)dealloc {[super dealloc];}
#end
A couple of things:
1) You should never call drawRect directly. Instead, call setNeedsDisplay or setNeedsDisplayInRect. See the Cocoa Drawing Guide or the UIView Class Reference for more info.
2) But that may not be source of your problem. From your code, it is difficult to tell what ends up in classLabel after you are done setting it up, but I expect it's not what you need. In particular, it needs a frame. I would suggest setting a CGRect variable to myClassLabel.frame and seeing what you end up with.
This is a follow on from another question regarding why I could not set UIControls in awakeFromNib. The answer to that is that as you can see below the controls are nil in the awakeFromNib, although they are initialised to the correct objects by the time we get to viewDidLoad. I setup the view the same as I always do, should I be doing something different to access them here, the xib(nib) was designed and saved with the current version of Image Builder.
CODE:
#interface iPhone_TEST_AwakeFromNibViewController : UIViewController {
UILabel *myLabel;
UIImageView *myView;
}
#property(nonatomic, retain)IBOutlet UILabel *myLabel;
#property(nonatomic, retain)IBOutlet UIImageView *myView;
#end
.
#synthesize myLabel;
#synthesize myView;
-(void)awakeFromNib {
NSLog(#"awakeFromNib ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"AWAKE"];
[super awakeFromNib];
}
-(void)viewDidLoad {
NSLog(#"viewDidLoad ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"VIEW"];
[super viewDidLoad];
}
OUTPUT:
awakeFromNib ...
myLabel: (null)
myView : (null)
viewDidLoad ...
myLabel: UILabel
myLabel: UIImageView
Much appreciated ...
gary
iboutlet was connect to control that was subview of view
and view was lazy load
so you can access it first like the following:
func awakeFromNib() {
super.awakeFromNib()
let _ = self.view
println("\(self.myLabel)")
}
This can happen, for example, if the View for the ViewController is loaded from another NIB. These connections are initialized and fixed only when that NIB is actually loaded, because they don't exist in the NIB that actually instantiates and awakens your controller in the first place.
I need some guidance on creating a UITableViewCell that has an image on the left which can be toggled. The image should be tappable and act as a toggle (checkbox).
My parts I'm struggling with are:
How do I detect taps on the image and handle those differently to didSelectRowAtIndexPath?
How do I change the image without performing a [tableView reloadData]?
It's actually pretty easy.
Just create a new subclass of UIControl and put it all in there (no need for a separate controller.) Let's call it ToggleImageControl.
#interface ToggleImageControl : UIControl
{
BOOL selected;
UIImageView *imageView;
UIImage *normalImage;
UIImage *selectedImage;
}
Create a ToggleImageControl for each cell, and add it at the appropriate position.
ToggleImageControl *toggleControl = [[ToggleImageControl alloc] initWithFrame: <frame>];
toggleControl.tag = indexPath.row; // for reference in notifications.
[cell.contentView addSubview: toggleControl];
Add a UIImageView to contain the image. Add a target for the touch event.
- (void) viewDidLoad
{
normalImage = [UIImage imageNamed: #"normal.png"];
selectedImage = [UIImage imageNamed: #"selected.png"];
imageView = [[UIImageView alloc] initWithImage: normalImage];
// set imageView frame
[self.view addSubview: imageView];
[self addTarget: self action: #selector(toggleImage) forControlEvents: UIControlEventTouchUpInside];
}
Set the UIImageView's image property to update the image; that will trigger the redraw without side-effects.
- (void) toggleImage
{
selected = !selected;
imageView.image = (selected ? selectedImage : normalImage);
// Use NSNotification or other method to notify data model about state change.
// Notification example:
NSDictionary *dict = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: self.tag forKey: #"CellCheckToggled"];
[[NSNotificationCenter defaultCenter] postNotificationName: #"CellCheckToggled" object: self userInfo: dict];
}
You will obviously need to massage some stuff. You probably want to pass in the two image names to make it more reusable, and also I'd recommend specifying the notification name string from outside the object as well (assuming you are using the notification method.)
Here's an implementation of the "override touchesBegan:" approach I'm using that is simple and seems to work well.
Just include this class in your project and create and configure a TouchIconTableViewCell instead of a UITableView cell in your tableView:cellForRowAtIndexPath: method.
TouchIconTableViewCell.h:
#import <UIKit/UIKit.h>
#class TouchIconTableViewCell;
#protocol TouchIconTableViewCellDelegate<NSObject>
#required
- (void)tableViewCellIconTouched:(TouchIconTableViewCell *)cell indexPath:(NSIndexPath *)indexPath;
#end
#interface TouchIconTableViewCell : UITableViewCell {
id<TouchIconTableViewCellDelegate> touchIconDelegate; // note: not retained
NSIndexPath *touchIconIndexPath;
}
#property (nonatomic, assign) id<TouchIconTableViewCellDelegate> touchIconDelegate;
#property (nonatomic, retain) NSIndexPath *touchIconIndexPath;
#end
TouchIconTableViewCell.m:
#import "TouchIconTableViewCell.h"
#implementation TouchIconTableViewCell
#synthesize touchIconDelegate;
#synthesize touchIconIndexPath;
- (void)dealloc {
[touchIconIndexPath release];
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [((UITouch *)[touches anyObject]) locationInView:self];
if (CGRectContainsPoint(self.imageView.frame, location)) {
[self.touchIconDelegate tableViewCellIconTouched:self indexPath:self.touchIconIndexPath];
return;
}
[super touchesBegan:touches withEvent:event];
}
#end
Each time you create or re-use the cell, set the touchIconDelegate and touchIconIndexPath properties. When your icon is touched, the delegate will be invoked. Then you can update the icon or whatever.
So the "..obviously need to massage some stuff.." comment means "...this code doesn't work...".
So
- (void) viewDidLoad
should be
- (id)initWithFrame:(CGRect)frame
{
if ( self = [super initWithFrame: frame] ){
normalImage = [UIImage imageNamed: #"toggleImageNormal.png"];
selectedImage = [UIImage imageNamed: #"toggleImageSelected.png"];
imageView = [[UIImageView alloc] initWithImage: normalImage];
// set imageView frame
[self addSubview: imageView];
[self addTarget: self action: #selector(toggleImage) forControlEvents: UIControlEventTouchDown];
}
return self;
}
As - (void) viewDidLoad never gets called.
It seems that Apple uploaded "TableMultiSelect" as sample codes on iOS Developer Program since 2011-10-12.
Multiple selection in edit mode can be enabled by this code.
self.tableView.allowsMultipleSelectionDuringEditing = YES;
http://developer.apple.com/library/ios/#samplecode/TableMultiSelect/Introduction/Intro.html
Though it can be used only from iOS5.
Several hours I could not find this sample code in Stack Overflow, so I added this info to this post.
There's an even EASIER way to do this, if you override touchesBegan: you need to do an if statement to decide if it's within the check marks proximity, if it's not call [super touchesBegan:touches withEvent:event] and it will act as though it was selected.
I do something similar (starring a favorite) like this, but I think you're demanding a lot from the thumbs of iPhone users with the cell directing people to another view. First of all, I would check out the detail disclosure option on cells. One of the options is a pre-made arrow button that you can attach to. Your call.
You might be able to get away with catching the didSelectRowAtIndexPath event and then doing some other logic instead of redirecting if the touch was on your checkbox, although I don't know how you would get the position. This means you might need to find a way to get ahold of the touch event before it calls didSelectRowAtIndex path, which I'm not quite sure how to do. Have you worked with handling touchesBegan and the like yet?