How to correctly set superclass properties from a subclass - iphone

I'm subclassing UITextField and want to assign to its rightView property a custom button.
I setup the button in setFrame of the subclass.
My textfields had their outlet connections in the ViewController and Class set to my subclass in Identity Inspector in Interface Builder.
The problem is that my custom button is not appearing.
I've tried to overcome the problem and made a setupRightView method in my subclass.
Then, when I call setupRightView on the outlet property from the View Controller - voila - everything is working just fine!
I'm fairly new to Objective-C and I suppose I'm making some stupid mistake with the object model.
Why I can't set correctly rightView property in my subclass?

Did you remember to do this:
self.rightViewMode = UITextFieldViewModeAlways;
That property is set to UITextFieldViewModeNever by default, so you won't see your button if you don't include that line.
After Edit: I checked on putting my code in setFrame, and it didn't work there -- not sure why. It worked fine if I put it in awakeFromNib.
-(void)awakeFromNib {
UIButton *myButton = [UIButton buttonWithType:2];
self.rightView = myButton;
self.rightViewMode = UITextFieldViewModeAlways;
}

Related

Cant change text of UITextView and UILabel

i have a viewcontroller with a textview and a label and i want to change the text on both of them before i load the view.
- (IBAction)loadCardInformation:(id)sender{
ciViewController = [[CardInformationViewController alloc] initWithNibName:#"CardInformationViewController" bundle:nil];
[ciViewController.infoLabel setText:#"ROFL"];
[ciViewController.infoText setText:#"CAKE"];
[self.view addSubview:(UIView *)ciViewController.view];
}
I have hooked both the label and the textview to outlets, but nothing is changing.
You need to set these in viewDidLoad of the viewController. At the point where you are currently setting the properties they have not been loaded.
Also
[self.view addSubview:(UIView *)ciViewController.view];
this looks like something you should most likely not be doing.

UIScrollView referenced both by UIViewController's 'view' property and by an outlet

I'm about to add a UIScrollView to my iPhone project and before I implement this functionality I wanted to check if my approach is the right one or if I could be violating some best practice I'm not aware of.
The tutorials I've seen generally involve adding a UIScrollView to an existing UIView and they work from there. However, I was wondering if I could spare the UIView altogether and just add the UIScrollView as the only top-level object in my nib file.
My sample project uses Xcode's View-based Application template:
Project navigator http://img542.imageshack.us/img542/5364/projectnavigator.png
I deleted the UIView top-level object from the original MySampleViewController.xib file and replaced it by adding a UIScrollView object:
Nib placeholders http://img526.imageshack.us/img526/7709/placeholderobjects.png
Now my nib file only shows this object in the canvas:
Canvas http://img233.imageshack.us/img233/4063/scrollview.png
Then I created the link from the UIViewController's view outlet to the UIScrollView.
Now, if I wanted to programmatically manipulate the contents of the UIScrollView I can use this code:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
// Solution B: With the following line we avoid creating an extra outlet linking to the UIScrollView top-level object in the nib file
UIScrollView *scrollView = (UIScrollView *)self.view;
for (int i = 0; i < colors.count; i++) {
CGRect frame;
//frame.origin.x = self.scroller.frame.size.width * i; // Solution A: scroller is an IBOutlet UIScrollView *scroller;
frame.origin.x = scrollView.frame.size.width * i; // Solution B
frame.origin.y = 0;
//frame.size = self.scroller.frame.size; // Solution A
frame.size = scrollView.frame.size; // Solution B
UIView *subView = [[UIView alloc] initWithFrame:frame];
subView.backgroundColor = [colors objectAtIndex:i];
//[self.scroller addSubview:subView]; // Solution A
[self.view addSubview:subView]; // Solution B
[subView release];
}
//self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * colors.count, self.scroller.frame.size.height); // Solution A
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * colors.count, scrollView.frame.size.height); // Solution B
}
In order to implement Solution A the scroller outlet must be linked to the nib's UIScrollView as well, and the Connections Inspector looks like this:
Connections Inspector http://img228.imageshack.us/img228/8397/connectionsj.png
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
Solution B only involves UIViewController's view outlet linking to the view, and using this line:
UIScrollView *scrollView = (UIScrollView *)self.view;
My questions:
Do I incur in some sort of violation of Apple's design guidelines by using one of these two solutions?
Should I stick to the UIScrollView within a UIView solution?
Is there any other way to implement this?
Thanks!
P.S. Sorry for the syntax highlight, SO didn't recognize the use of the 'objective-c' tag
No I think you are fine either way.
I would, I don't think a UIView has any significant cost, plus what if you want to add a page control? and you don't have to cast the controller's view to a UIScrollView every time you need it.
Looks like you have it under control to me.
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
It standard to have IBOutlets to any view defined in your .nib that you want to access directly from your view controller.
If you don't want two outlets you could give the scroll view a tag then find it like so:
UIScrollView *myScrollView = (UIScrollView *)[self.view viewWithTag:1]
Then you only have the view as an outlet, but I would just add the extra outlet. Just make sure you set them to nil in your viewDidUnload.
Also you don't have to retain the scroll view (if you are even still using retain/release). Since the scroll view is inside your view controller's view it keeps a reference so you can have your scrollview's property by assign or week if your using ARC.
Hope that helps.

Background color for subclassed UIButton?

I have a UIButton subclass that I initialize as:
MyButton *button = [MyButton buttonWithType:UIButtonTypeCustom];
I want to set the background color of this button.
If I do the following in the view controller after creating the button:
MyButton.backgroundColor = [UIColor ...];
It works fine. But if I try to do the following within the UIButton subclass, either in initWithFrame or in DrawRect:
self.backgroundColor = [UIColor ...];
It simply does nothing. The button remains transparent.
The thing is, my logic is such that I really need to set the color inside the UIButton subclass, not in the calling code.
Any ideas?
UIButton is not meant to be subclassed. It is a class cluster and you almost certainly get it wrong (it may break now or in the future). The base method buttonWithType: also will never return an instance of your class, so you need to go to great lengths to make all your code work.
The much better way is to to make a factory method that uses UIButton.buttonWithType: and configures the button the way you need it.
Can you implement "Buttonwithtype" method in your subclass and write code there self.backgroundColor = [UIColor WhateverColor];
It should work. let me know.

Using xib object inside another xib

I'm designing using IB a specific button (with some labels,and imageView and other stuff).
I would like then to use that xib object (the button) in another xib, where I have 6 objects of that button.
I know I can do that programmatically using the Class Identifier, but then I have to position my lables and image view, and set the default values and everything else in code.
I'm wondering if it's possible to do the same thing using just the IB ? (of course I would still set the values in code, but I want to positions of the lables/imageView and all the rest to be set in the xib)
Thanks
Edit
So, I understand what I asked for at first is not possible.
What I'm trying now is like that :
I created a ButtonTest.xib.
Inside the Xib I have a UIView and 3 subviews (2 lables and a button).
In the inspector I set the UIView class to ButtonTest.
Inside ButtonTest.m I have 3 outlets for each of the subviews, which I connect in IB.
Next I have a ButtonTestViewController.xib.
Inside it I put one view and set it's class in the inspector to be ButtonTest.
I connect that view to a myTextView outlet inside ButtonTestViewController.m of class ButtonTest
Now, this is the code inside ButtonTestViewController.m viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"ButtonTest" owner:nil options:nil];
ButtonTest *mainView = (ButtonTest*)[subviewArray objectAtIndex:0];
self.myTextView = mainView;
}
What I hoped would happen, is that the view in ButtonTestViewController.xib would become the view I designed in ButtonTest.xib.
This just isn't happening. what happens is that the view inside ButtonTestViewController.xib stays the same.
Worth mentioning, is that if I add:
[self.view addSubview:mainView];
It does add the new view, besides the existing one.
Any idea how to do what I want ?
Eventually I would like to have 6 views in ButtonTestViewController.xib, all would look like the ButtonTest.xib template, and the lables values will be set in code.
Edit2
Ok, guys I did everything you said and it worked like a charm.
The only problem I have right now with this issue is when the view in ButtonTestViewController.xib is a little bigger then view in ButtonTest.xib.
When that happens, The lable on the button look extremely blurry.
When they are both the same size, it's all good.
# Ned - I used the exact code you posted in my InitWithCoder method, except I switched the frame sentence to this :
self.bounds = mainView.frame;
I tried playing with the content mode in IB trying to fix it, to no avail.
When I do use it like you posted, meaning :
mainView.frame = self.bounds;
It's not working at all, and both views' sizes stay the same.
Here I tried playing with the resizeMask but still didn't work.
And idea how to fix these 2 issues ?
Thanks guys!
Edit 3
I did manage to fix one of the issues, resizing the ButtonTestViewController.xib to the ButtonTest.xib view size.
This is what I did (using code to solve the blurry issue, taken from here)
self.bounds = CGRectMake(0, 0, mainView.frame.size.width, mainView.frame.size.height);
CGRect overlay2Frame = self.frame;
overlay2Frame.origin.x = round(overlay2Frame.origin.x);
overlay2Frame.origin.y = round(overlay2Frame.origin.y);
self.frame = overlay2Frame;
The other issue I still can't solve. the view in ButtonTest.xib just won't get bigger to match the other view.
You can do this, and pretty easily. The way I do this is create a UIView subclass (let's say it's called "MyView.m" and "MyView.h"), and then a nib called "MyView.xib".
First, in the nib, click File's Owner, and set the class to "MyView". This will make it so IBOutlets/Actions from your class show up in IB. Add a single top-level view (if it doesn't have one already) as your main view, and add your other custom elements (UILabels and UIImageViews) as subviews of the main UIView.
Next, add the following code so that it gets called when MyView is initialized (remember, if you initialize from a nib it'll get initialized via - (id)initWithCoder:(NSCoder *)aDecoder).
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
UIView *mainView = [subviewArray objectAtIndex:0];
//Just in case the size is different (you may or may not want this)
mainView.frame = self.bounds;
[self addSubview:mainView];
What this does is loads the view hierarchy from your nib into the NSArray, and since you only had one top-level UIView, you just add that as a subview of your custom UIView.
This allows you to design UIViews (and other subclasses of UIView) in Interface Builder.
EDIT: Updated to OP's edit.
The way I've always done this is by first following my directions above. One difference is that in your ButtonTest nib you're changing the class of the UIView to ButtonTest, but I change the File Owner's class to ButtonTest. Then, inside ButtonTest.m, do the loadNibNamed stuff. Now, to add that object to ButtonTestViewController.xib, add a UIView (or UIButton, whatever ButtonTest is subclassed from) and change the class to ButtonTest.
This is a little confusing, so I'll try to break it up by file.
ButtonTestViewController.xib: Add a UIButton (of whatever ButtonTest inherits from) and change the class to ButtonTest
ButtonTest.m: Inside the "initWithCoder" method, do all of the "loadNibNamed" stuff I have above
ButtonTest.xib: Change File's Owner class to ButtonTest and link up all IBOutlets and IBActions
Some of you ask in comment if this solution doesn't create infinite loop and I don't have enough reputation to answer there, so here is my answer:
The loop exist if root view in your XIB has "Custom class" set to MyView. This causes the situation where view loaded from XIB invokes initializer of MyView again and therefore, creates infinite loop. The solution is that "Custom class" should be UIView and only File Owner's class should be set to MyView (to be able to assign IBOutlets and IBActions).
Swift 3.0
create swift file with class
class ViewXIBfileClassView: UIView {
.....
}
create Xib file with view:ViewXIBfileClassView
create wrapper view class
class ViewWrapper: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let view = UINib(nibName: "ViewXIBfileClassView_file", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! viewXIBfileClassView
self.addSubview(view)
view.bindEdgesToSuperview()
}
}
add extension to UIView
extension UIView {
/// Adds constraints to the superview so that this view has same size and position.
/// Note: This fails the build if the `superview` is `nil` – add it as a subview before calling this.
func bindEdgesToSuperview() {
guard let superview = superview else {
preconditionFailure("`superview` was nil – call `addSubview(view: UIView)` before calling `bindEdgesToSuperview()` to fix this.")
}
translatesAutoresizingMaskIntoConstraints = false
["H:|-0-[subview]-0-|", "V:|-0-[subview]-0-|"].forEach { visualFormat in
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: visualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
}
}
}
Use UIView with ViewWrapper inside all Xib files you want
If your class is a subclass of UIButton for example, you can open the Library in Interface Builder and it will be under the classes tab when you search for it and you can drag and drop.
You can also just drag/drop a UIButton and set its class in the inspector pane.
However, because you have your own .xib that might mess it up, in which case just ignore me.

iOS tableFooterView: Can't use UIButton?

I made a custom UIViewController with an UIImageView and a UIButton in it. In the tableView viewForFooterInSection method I returned the view, and it displays correctly(i can see the button). However, I can't touch the button. It won't depress/highlight, and my connected IBAction doesn't fire.
Also, I have an object from the camera called takenPicture. myImage is a UIImageView IBOutlet property of customView. The imageView is always empty.
I'm not sure what I'm doing wrong, but here is my code that is in the viewForFooterInSection method:
self.customView = [[CustomView alloc] initWithNibName:#"CustomView" bundle:nil];
customView.myImage.image = self.takenPicture;
return customView.view;
It sounds as if the frame/bounds isn't set correctly for your view (the one containing the button and image view). Likely it is defaulted with CGRectZero, and the reason you can see the button and image is because clipping is off. But touch events wont filter up to the button if it is outside the bounds of its superview.