(this question probably needs a more descriptive title, feel free to improve it)
I have a UIView subclass with a property:
#property (weak, nonatomic) UILabel *label;
In an initialize method I have:
[self addSubview: (self.label = [UILabel new])];
I like the terseness of this, but I have questions how it works.
For one, I get a warning:
Assigning retained object to weak property; object will be released after assignment
Warning aside, it actually seems to work. Is that because before the release machinery can run, the addSubview: re-retains it?
If I understand correctly, the self.label = ... code is just sugar for a [self setLabel: ...]. But if I override the property access with my own setLabel: implementation, its signature will be
- (void) setLabel: (UILabel*) label;
So the return value is void. But it's being fed into the the addSubview: send and working? So how does that work?
Update
One of the things that frustrates doing a one liner here, is that Objective-C tend to eschew returning useful information from methods like addSubview: which is divergent from the Smalltalk influence which spawned Objective-C in the first place. In Smalltalk, it would be expected/common for the addSubview: method to return the added object. If this was the case, one could write these expressions as:
self.label = [self addSubview: [UILabel new]];
This would allow the strong/weak semantics to be happy. The addSubview: would do the strong retain before the it got to the weak label setter.
I fight this problem with both subviews and recognizers, so I thought I'd be clever, and write a category to do something like that (I inverted the receiver/argument so it would be easily distinguishable from an alternate addSubview: signature).
#interface UIView (UIView_Adding)
- (UIView*) addedToView: (UIView*) superview;
#end
#interface UIGestureRecognizer (UIView_Adding)
- (UIGestureRecognizer*) addedToView: (UIView*) view;
#end
#implementation UIView (UIView_Adding)
- (UIView*) addedToView: (UIView*) superview {
[superview addSubview: self];
return self;
}
#end
#implementation UIGestureRecognizer (UIView_Adding)
- (UIGestureRecognizer*) addedToView: (UIView*) view {
[view addGestureRecognizer: self];
return self;
}
#end
Unfortunately, this just invokes the Law of Conservation of Ugly. I get rid of one kind of warning and get another. With that category included, I can now write:
self.label = [[UILabel new] addedToView: self];
But this generates a warning that self.label is meant to hold a specific subclass of UIView, namely a UILabel and the return type of addedToView: is `UIView'. I'm not aware of an Objective-C pseudo type which means be the right darn subtype of this known super type to make property types happy. :(
Final Update
I discovered the instancetype type. By changing my category method signatures to return these types, everything just works. I'm new enough to instancetype to not know if I'm abusing something here, but I'm thrilled it worked.
The warning comes from:
self.label = [UILabel new]
since your label property is weak.
You are correct that it ends up working because the label is retained by the call to addSubview:.
But if the label is ever removed from its superview, the label property will become nil and the label will be lost. So if there is any chance that the label will be removed but you want to keep a reference to the label (maybe to add it back later), then change your property to be strong.
You are also correct that self.label = is really just [self setLabel:]. The reason that this can be passed to addSubview: is due to the fact that an expression such as x = y has a value equal to the assignment.
So:
[self addSubview: (self.label = [UILabel new])];
if equivalent to:
UIView *view = (self.label = [UILabel new]);
[self addSubview:view];
Related
my didAddSubview is not being called.
This is what I am doing.
In my viewController
- (void)viewDidLoad {
[super viewDidLoad];
SomeView *view = [[SomeView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame))];
[self.view addSubview:view];
}
Where my SomeView is a UI view (.h file)
#import <UIKit/UIKit.h>
#interface DyteMeetingView : UIView
#end
in which I have didAddSubview like this
- (void)didAddSubview:(UIView *)subview{
[super didAddSubview:subview];
[self someFunc];
}
I placed debugger in didAddSubview but the debugger never reaches here and hence [self someFunc]; is also not called.
Since didAddSubview is a lifecycle method for UI view, I am also not calling it from anywhere.
Can someone help me in figure out the concept I missed out on/what I could be doing wrong?
PS: I don't have any xib file for UIView.
PS: Intentionally added swift tag as well since there isn't large chunk of code and I am confident that swift developers would be able to understand the question/code and might be able to help me out.
didAddSubview in SomeView will be called when a sub view is added to an instance of SomeView, but that isn't what you are doing here. You are adding a SomeView as a subview of another view.
You want didMoveToSuperview; in this method you can use the superview property of self to identify the view that it moved to.
The didAddSubview will be called in viewController.view, instead of your SomeView, because you using viewController.view.addSubview(subview)
I have a few custom UIView objects that all handle drawing like this:
- (void)drawRect:(CGRect)rect {
// ^ I init the layers 1 and 2
[self.layer insertSublayer:layer1 atIndex:0]; // 1 or more
[self.layer insertSublayer:layer2 atIndex:1];
}
They also have a - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; with nothing else inside but a NSLog.
I add them all inside my main ViewController like so:
- (void)viewDidLoad
{
[super viewDidLoad];
CustomView *myViewWith1Layer = [[CustomView alloc] initWithFrame:CGRectMake(20, 20, 440, 260)];
[self.view addSubview:myViewWith1Layer];
CustomViewLayered *myViewWith2Layer = [[CustomViewLayered alloc] initWithFrame:CGRectMake(40, 260, 200, -120)];
[self.view addSubview:myViewWith2Layers];
}
When I run my app, if I tap on a view that has only a single layer - I get my NSLog to show, and everything's fine. If, on the other hand, I tap on views with 1+ layers, the app crashes (objc_msgSend log shows up with "EXC_BAD_ACCES (code=1, address=..."). I guess this is somehow related with ARC, which I have enabled.
How do I add multiple layers to a view, without it being messed up by ARC?
I don't think that this is an ARC problem, but creating and inserting the layers in drawRect
is wrong. This should be done (only once) in the init method of the view, e.g. in initWithFrame.
In my case, the solution was definitely related to ARC.
When initialising my delegates, I immediately assigned them to the layer.delegate property, and ARC would remove the object from existence immediately after that.
So for each layer, I add a strong #property (strong, nonatomic) delegatesClass *delegatesName and initialise straight to the property. After that, I assign the layer.delegate = self.delegatesName.
This did solve the issue, although I'm not sure if it is the right way to do things.
I am mocking up a quick demo of a project but am having a problem with a UITextField.
The behavior that we want is that when a user clicks on a button, there should be a custom view that appears with a UITextField and a UIButton in a custom view that overlays the main view.
I have a custom view called Searchview and the following in the Searchview.m. The problem is that when the textField is a property, it doesn't show but when it is a local variable, it does show. Can anybody help me with what is going on so that the UITextField shows? Is how I am doing this even the right idea (custom UIView or custom UIControl or a modal controller)? Finally, would setNeedsDisplay be appropriate here?
thx in advance
#interface Searchview()
#property (nonatomic, weak) UITextField *textField;
#end
- (void)drawRect:(CGRect)rect
{
// this doesn't work
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 120.0f, 25.0f)];
self.textField.returnKeyType = UIReturnKeyDone;
self.textField.placeholder = #"Writer";
self.textField.borderStyle=UITextBorderStyleBezel;
[self.textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: self.textField];
/* this works
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 120, 25)];
textField.returnKeyType = UIReturnKeyDone;
textField.placeholder = #"Writer";
textField.borderStyle=UITextBorderStyleBezel;
[textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: textField];
*/
UIButton *mButton=[UIButton buttonWithType:UIButtonTypeRoundedRect];
mButton.frame=CGRectMake(200.0f,10.0f,100.0f,37.0f);
[mButton setTitle:#"search" forState:UIControlStateNormal];
[mButton addTarget:self action:#selector(showSearchController:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:mButton];
[self setNeedsDisplay];
}
As a property - not showing:
As a local variable - showing:
#property (nonatomic, strong) UITextField *textField;
change the weak to strong and change the self.textFiled to _textField to have a try
And make sure your textField property not be released
It's pretty simple when you think about it. ARC (approximately) converts the following code:
self.weakProp = [[Foo alloc] init];
to the equivalent of the following "manually reference-counted" code:
Foo * temp = [[Foo alloc] init];
self.weakProp = temp;
[temp release];
Nothing is retaining it, so it is released.
I can only think of two reasons to have assign/weak IBOutlets:
For an outlet in a VC, so it doesn't retain a subview when its view is set to nil (e.g. on a memory warning). This is less relevant in iOS 6.0 since views are not automatically released on a memory warning (so if you do it, you can release them all explicitly).
For a view where the outlet points to a superview (and would cause a retain cycle). This is quite rare.
In general, I prefer strong IBOutlets: They might keep objects alive for a little longer than necessary, but they are safer than assign and more efficient than weak. Just watch out for retain cycles!
I have some custom appearance properties in my view class (a descendant of UIView). I want to customize the view appearance according to these properties, but I can’t do that inside the initializer, since the values set using [[MyClass appearance] setFoo:…] aren’t in effect at that point:
#interface View : UIView
#property(strong) UIColor *someColor UI_APPEARANCE_SELECTOR;
#end
#implementation View
#synthesize someColor;
// Somewhere in other code before the initializer is called:
// [[View appearance] setSomeColor:[UIColor blackColor]];
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", someColor); // nil
return self;
}
#end
They are already set in layoutSubviews, but that’s not a good point to perform the view customizations, since some customizations may trigger layoutSubviews again, leading to an endless loop.
So, what’s a good point to perform the customizations? Or is there a way to trigger the code that applies the appearance values?
One possible workaround is to grab the value directly from the proxy:
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", [[View appearance] someColor); // not nil
return self;
}
Of course this kills the option to vary the appearance according to the view container and is generally ugly. Second option I found is to perform the customizations in the setter:
- (void) setSomeColor: (UIColor*) newColor
{
someColor = newColor;
// do whatever is needed
}
Still I’d rather have some hook that gets called after the appearance properties are set.
Why not wait until
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview) {
... code here ...
}
}
if it's giving you trouble?
I believe UIAppearance properties are applied to a view when it is being added into a view hierarchy. So presumably you could access the set properties in UIView didMoveToSuperview.
Caveat: I am using Swift 2, so not sure about earlier versions of Swift / Objective-C. But I have found that didMoveToSuperview() will not work. The properties are available in layoutSubviews(), but that's not a great place to do anything like this (since it can be called more than once). The best place to access these properties in the lifeCycle of the view I have found is didMoveToWindow().
I would have thought that viewDidLoad would be best if it's a one-time thing. Otherwise, viewWillAppear.
EDIT:
If you want to do it in the view, and not it's controller then I would create a custom init for the view along the lines of:
-(id) initWithFrame:(CGRect) frame andAppearanceColor:(UIColor)theColor;
thereby passing the colour into the view at creation time.
I know this is really basic stuff but i need to understand whether my understanding of this is correct.
So what i want to do is this. I want an view with a label on which when double tapped flips and loads another view. On the second view i want a UIPickerView and above i have a button saying back. Both views will be of same size as an UIPickerView which is 320px x 216px.
What i am thinking of to do is create two UIViewclasses named labelView and pickerView. I would then create a viewController which on loadView loads labelView then when user double taps the labelView i get an event in labelView class which is sent to my viewController that then can unload loadView and load the pickerView.
Does this sound as the best way to do this ? Is there a simpler way ? I am also unsure how i route the event from the labelView class to the viewControllerclass.
I dont exactly know the most efficient way to do it(as i am also now to this language),but it is for sure that i have solved ur problem. I made a simple program for that.Three classes involved here in my eg are BaseViewController (which will show two views),LabelView and PickerView (according to ur requirement).
In LabelView.h
#protocol LabelViewDelegate
-(void)didTapTwiceLabelView;
#end
#interface LabelView : UIView {
id <LabelViewDelegate> delegate;
}
#property(nonatomic,retain)id <LabelViewDelegate> delegate;
-(void)didTouch;
#end
In LabelView.m
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UILabel* labl = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, frame.size.width-20,20)];
labl.text = #"Some Text";
[self addSubview:labl];
[labl release]; labl = nil;
self.backgroundColor = [UIColor grayColor];
UITapGestureRecognizer* ges = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTouch)] autorelease];
ges.numberOfTapsRequired = 2;
[self addGestureRecognizer:ges];
}
return self;
}
-(void)didTouch
{
[delegate didTapTwiceLabelView];
}
//=============================================================
In Pickerview.h
#protocol PickerViewDelegate
-(void)didTapBackButton;
#end
#interface PickerView : UIView <UIPickerViewDelegate,UIPickerViewDataSource>{
id <PickerViewDelegate> delegate;
}
#property(nonatomic,retain)id <PickerViewDelegate> delegate;
#end
In Pickerview.m
#implementation PickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 30, 320, 216)];
picker.delegate = self;
picker.dataSource = self;
[self addSubview:picker];
[picker release]; picker = nil;
self.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 250);
UIButton* btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10, 1, 50, 27)];
[btn setTitle:#"Back" forState:UIControlStateNormal];
[btn addTarget:self action:#selector(backButton) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
return self;
}
-(void)backButton
{
[delegate didTapBackButton];
}
//====================================================================
in BaseViewController.h
#import "LabelView.h"
#import "PickerView.h"
#interface VarticalLabel : UIViewController<UITextFieldDelegate,PickerViewDelegate,LabelViewDelegate> {
PickerView* myPickerView;
LabelView* myLabelView;
}
#end
In BaseViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
myPickerView= [[PickerView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myPickerView];
myPickerView.delegate = self;
myLabelView= [[LabelView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myLabelView];
myLabelView.delegate = self;
myPickerView.hidden = YES;
}
#pragma mark PickerViewDelgate
-(void)didTapBackButton
{
myPickerView.hidden = YES;
myLabelView.hidden = NO;
}
#pragma mark LabelViewDelegate
-(void)didTapTwiceLabelView
{
myPickerView.hidden = NO;
myLabelView.hidden = YES;
}
To get events from a button to the view controller, just hook up the button's event, e.g. touch up inside, to a method in the view controller, using interface builder. (Double tapping is probably more complicated though.)
When you say 'flips', do you mean it actually shows an animation of flipping over a view to show a 'reverse' side? Like in the weather app when you hit the 'i' button? I'm assuming this is what you mean.
Perhaps check TheElements sample example on the iPhone Reference Library, it has an example of flip animation.
Btw, it's not strictly necessary to unload the loadView that is being 'hidden' when you flip -- it saves you having to construct it again when you flip back -- but it may be pertinent if you have memory use concerns, and/or the system warns you about memory being low.
Also, what do you mean by "create a UIView"? Do you mean subclass UIView, or just instantiate a UIVIew and add children view objects to it? The latter is the usual strategy. Don't subclass UIView just because you want to add some things to a UIView.
If you've got one screen of information that gives way to another screen of information, you'd normally make them separate view controllers. So in your case you'd have one view controller with the label and upon receiving the input you want, you'd switch to the view controller composed of the UIPickerView and the button.
Supposing you use Interface Builder, you would probably have a top level XIB (which the normal project templates will have provided) that defines the app delegate and contains a reference to the initial view controller in a separate XIB (also supplied). In the separate XIB you'd probably want to add another view controller by reference (so, put it in, give it the class name but indicate that its description is contained in another file) and in that view controller put in the picker view and the button.
The point of loadView, as separate from the normal class init, is to facilitate naming and linking to an instance in one XIB while having the layout defined in another. View controllers are alloced and inited when something that has a reference to them is alloced and inited. But the view is only loaded when it is going to be presented, and may be unloaded and reloaded while the app is running (though not while it is showing). Generally speaking, views will be loaded when needed and unnecessary views will be unloaded upon a low memory warning. That's all automatic, even if you don't put anything in the XIBs and just create a view programmatically within loadView or as a result of viewDidLoad.
I've made that all sound more complicated than your solution, but it's actually simpler because of the amount you can do in Interface Builder, once you're past the curve of learning it. It may actually be worth jumping straight to the Xcode 4 beta, as it shakes things up quite a lot in this area and sites have reported that a gold master was seeded at one point, so is likely to become the official thing very soon.
With respect to catching the double tap, the easiest thing is a UITapGestureRecognizer (see here). You'd do something like:
// create a tap gesture recogniser, tell it to send events to this instance
// of this class, and to send them via the 'handleGesture:' message, which
// we'll implement below...
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
// we want double taps
tapGestureRecognizer.numberOfTapsRequired = 2;
// attach the gesture recogniser to the view we want to catch taps on
[labelView addGestureRecognizer:tapGestureRecognizer];
// we have an owning reference to the recogniser but have now given it to
// the label. We don't intend to talk to it again without being prompted,
// so should relinquish ownership
[tapGestureRecognizer release];
/* ... elsewhere ... */
// the method we've nominated to receive gesture events
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// could check 'gestureRecognizer' against tapGestureRecognizer above if
// we set the same message for multiple recognisers
// just make sure we're getting this because the gesture occurred
if(gestureRecognizer.state == UIGestureRecognizerStateRecognized)
{
// do something to present the other view
}
}
Gesture recognisers are available as of iOS 3.2 (which was for iPad only; so iOS 4.0 on iPhone and iPod Touch).