I'm trying to lay out some images in code using addSubview, and they are not showing up.
I created a class (myUIView) that subclasses UIView, and then changed the class of the nib file in IB to be myUIView.
Then I put in the following code, but am still getting a blank grey screen.
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self setupSubviews];
}
return self;
}
- (void)setupSubviews
{
self.backgroundColor = [UIColor blackColor];
UIImageView *black = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"black.png"]];
black.center = self.center;
black.opaque = YES;
[self addSubview:black];
[black release];
}
yes, just implement initWithCoder.
initWithFrame is called when a UIView is created dynamically, from code.
a view that is loaded from a .nib file is always instantiated using initWithCoder, the coder takes care of reading the settings from the .nib file
i took the habit to do the initialization in a separate method, implementing both initWithCode and initWithFrame (and my own initialization methods when required)
try implementing initWithCoder: sometimes I've had trouble with IB and initWithFrame:
or at least add a logging call to see if your init method is executed
Related
I have an app with several SKScenes. To keep it snappy, I have a single UIViewController, that handles 1 SKView.
I'm trying to add a UICollectionView to one of the SKScenes. However the problem comes when I try to set the delegate for the collection view to the SKScene initialising it.
I initialise it here:
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
//Initialise collectionView
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height) collectionViewLayout:layout];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView setBackgroundColor:[UIColor redColor]];
}
return self;
}
After initialising, I add the collection view as a subview of the UIViewController's view by calling:
- (void)didMoveToView:(SKView *)view
{
[self.view addSubview:_collectionView];
}
But the delegate or datasource methods aren't called. I've set up the header:
#interface BrowseScene : SKScene <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
I'm guessing that despite setting self as the delegate, once the collection view is added, it is added as a subview of the UIViewController's SKView, and therefore has no reference to the SKScene that initialised it. I tried calling:
_collectionView.delegate = self.view.scene;
But I get a compiler error saying SKScene is an incompatible type for UICollectionViewDelegate.
So my question is, how best to approach this?
It turns out, the problem lay in how I was initialising the UICollectionView, not where. And it's perfectly ok to set an SKScene as the delegate class.
The main reason the delegates weren't being called was because I'd initialised the collectionView with an empty layout object (as UICollectionViewLayout is just an abstract class). I initialised with a standard UICollectionViewFlowLayout instead, and the methods were then called when the class initialised.
I was also missing a call to
[_collectionView registerClass:[collectionViewCell class] forCellWithReuseIdentifier:#"collectionViewCell"];
(In the complete code, I had calls to reloadData, so that wasn't the problem here).
I have a UIViewController with some controllers and some views. Two of these views (Grid Cell) are other nibs. I've got outlets from the Grid Cells to File's Owner, but they aren't loaded automatically.
So I try to override GridCell.m's initWithCoder.
This starts an infinite loop.
I know it's possible to just override initWithFrame and add the subview from code, but this is not what I want. I want to be able to move the view around in Interface Builder and have Xcode initialize the view with the right frame.
How do I go about achieving this?
EDIT 1
I'm trying to get it working with the help of Alexander. This is how I've now got it set up:
MainView has UIView with a Custom class set as GridCell. It got an outlet in the MainView/File's Owner.
Removed all init-code from GridCell.m and set up an outlet to my custom class
The MainView don't still display the GridCell though. There's no error, just a lonely, empty space where the red switch should be. What am I doing wrong?
I'm very close to just doing this programmatically. I would love to learn how to this with nibs though.
Loading the nib causes initWithCoder to be called again, so you only want to do so if the subclass currently doesn't have any subviews.
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
if (self.subviews.count == 0) {
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil];
UIView *subview = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
subview.frame = self.bounds;
[self addSubview:subview];
}
}
return self;
}
Loading a nib will cause the corresponding owner in a
-(id) initWithCoder:(NSCoder *) coder;
call
Therefore your coude in this method:
self = [[[NSBundle mainBundle] loadNibNamed: #"GridCell"
owner: self
options: nil] objectAtIndex:0];
will cause again a call of the initWithCoder method. That's because you try to load the nib again. If you define a custom UIView and create a nib file to lay out its subviews you can't just add a UIView to another nib file, change the class name in IB to your custom class and expect the nib loading system to figure it out.
What you could do is the following:
Your custom view's nib file needs to have the 'File's owner' class set to your custom view class and you need to have an outlet in your custom class called 'toplevelSubView' connected to a view in your custom view nib file that is acting as a container for all the subviews. Add additional outlets to your view class and connect up the subviews to 'File's owner' (your custom UIView). (See https://stackoverflow.com/a/7589220/925622)
EDIT
Okay, to answer your edited question I would do the following:
Go to the nib file where you want to include the custom view with it's nib file layouting it.
Do not make it to the custom view (GridCell) itself, instead make a view which will contain your grid cell (gridCellContainer for example, but it should be a UIView)
Customize the initWithFrame method within your custom view like you did in initWithCoder:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"GridCell" owner:self options:nil];
self = [nib objectAtIndex:0];
self.frame = frame;
}
return self;
}
And then, in the viewController which is the fileOwner for the view where you want to include your custom view (the one with the gridCellContainer view) do this in viewDidLoad e.g.
//...
GridCell *gridCell = [[GridCell alloc] initWithFrame:self.viewGridCellContainer.bounds];
[self.viewGridCellContainer addSubview:gridCell];
Now eveything should work as you expected
The File's owner will not get a call to
-(id) initWithCoder:(NSCoder *) coder;
when loading a xib.
However, every view defined in that xib will get a call to
-(id) initWithCoder:(NSCoder *) coder;
when loading a xib.
If you have a subclass of a UIView (i.e. GridCell) defined in a xib and also try to load that same xib in your subclass's initWithCoder, you will end up with an infinite loop. However, I can't see what will the use case be.
Usually you design your UIView's subclass (i.e. GridCell) in one xib, then use that subclass in a view controller’s xib for example.
Also, can't see a use case where your custom view will have a subview in it's initWithCoder, i.e.
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
if (self.subviews.count == 0) {
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil];
UIView *subview = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
subview.frame = self.bounds;
[self addSubview:subview];
}
}
return self;
}
Unless you want to be able to override its view hierarchy on demand in some other xib. Which IMO assumes an external dependency (i.e. a hierarchy defined in another xib) and kinda defeats the purpose of having a reusable UIView in the first place.
Bare in mind that when loading a xib, passing an instance as the File's owner, will have all of its IBOutlet(s) set. In that case, you would be replacing self (i.e. GridCell) with whatever the root view in that GridCell.xib is, losing all IBOutlet connections in the process.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"GridCell" owner:self options:nil];
self = [nib objectAtIndex:0];
self.frame = frame;
}
return self;
}
There is a more detailed post on "How to implement a reusable UIView." that goes into a bit more detail as well and hopefully clears things up.
loadNibNamed:: will call initWithCoder:
Why don't you follow this pattern?
-(id)initWithCoder:(NSCoder *)coder
{
if (self = [super initWithcoder:coder]) {
// do stuff here ...
}
return self;
}
Does [super initWithcoder:coder] do things that you want to avoid?
I had the same issue when I'm trying to override initWithsomething method, we need
-(id)initWithsomething:(Something *)something
{
if (self = [super initWithsomething:something]) {
// do stuff here ...
}
return self;
}
instead
-(id)initWithsomething:(Something *)something
{
if (self = [super init]) {
// do stuff here ...
}
return self;
}
I'm trying to do draw a fancy UIButton using Quartz. I've declared my own button class like this:
#interface MyButton : UIButton
#end
In the .m file I'm constructing the button:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
CALayer *buttonLayer = self.layer;
buttonLayer.masksToBounds = YES;
CALayer *customDrawn = [CALayer layer];
customDrawn.delegate = self;
customDrawn.masksToBounds = YES;
[buttonLayer insertSublayer:customDrawn atIndex:0];
[customDrawn setNeedsDisplay];
}
return self;
}
But this results in some kind of recursion and finally fails with a EXC_BAD_ACCESS.
I've implemented a method drawLayer: inContext:, but it still crashes. The only way I can avoid the crash is by removing the delegate-assignment, but then I can't do any of the custom drawing I want to implement.
How can I make this work?
As described in the question mentioned by Kurt Revis, a UIView (such as UIButton) can't be used as a delegate for a sublayer. (It is already the delegate for the "main" layer of the UIView and can't be used as a delegate for another layer.) The solution is to use another object as a delegate which can be a "private" class just used to implement the drawLayer: inContext: method.
I have a custom view to which I need to add a couple of subviews programmatically. Which is the best place to create and add these subviews? Apple docs say:
If your view class manages one or more integral subviews, do the following: Create those subviews during your view’s initialization sequence.
This is a bit unclear to me. Should I handle that in initWithFrame, initWithCoder or somewhere else?
Note that I'm not talking about controllers here, this is a View that needs to initialize itself.
initWithFrame is the method you call to create a UIView programmatically, while initWithCoder is called when a UIView is instanciated from a XIB.
So it all depends on how you're going to create your containing view.
A way to cover all cases :
- (id) initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])){
[self setUpSubViews];
}
return self;
}
- (id) initWithCoder:(NSCoder*)aDecoder
{
if ((self = [super initWithCoder:aDecoder])){
[self setUpSubViews];//same setupv for both Methods
}
return self;
}
- (void) setUpSubViews
{
//here create your subviews hierarchy
}
Do the following: take the method whichever is the designated initializer for your view. That is, it's the most configureable (i. e. most arguments) init... method that every other initializer calls. For example, it can be -initWithFrame:, as most commonly. Then implement this method as follows:
- (id) initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// add new views here, for example:
UIImageView *imageView = [[UIImageView alloc] initWithImage:anImage];
[self addSubview:imageView];
[imageView release];
}
return self;
}
Generally , you should create the view and [self addSubView] in viewdidload/viewwillappear of the parent view controller if you want to show the view all the time. You can also set it hidden if you wih. In some other scenario like you want to show a view on a button click or some other actions, you should add subviews accordingly.
By integral, they mean either important or massive. viewDidLoad works just fine, but for really big, or important stuff, initialization methods are the way to go.
I would like to build a custom init method for a UIViewController, but after digging around on the Internet and specifically in SO I am confused about designated initializers.
I have a subclass of an UIViewController with these two initializers:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if ( self ) {
}
return self;
}
- (id) initWithFilename:(NSString *)aFilename {
self = [self initWithNibName:#"WallpaperDetailsViewController" bundle:nil];
if ( self ) {
self.filename = aFilename;
}
return self;
}
Then I have a viewDidLoad method that customizes the view according to the filename property:
- (void)viewDidLoad {
[super viewDidLoad];
// Create a UIImageView to display the wallpaper
self.wallpaper = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.filename]];
// ...
}
In another UIViewController I make the following call:
WallpaperDetailsViewController *detailsViewController = [[WallpaperDetailsViewController alloc] initWithFilename:#"foobar.png"];
[[self navigationController] pushViewController:detailsViewController animated:YES];
The result is that viewDidLoad is being called as a consequence of [self initWithNibName:], which does not initialize the UIImageView because self.filename is null.
According to other SO questions and answers, that should be the expected behavior. I am not sure about this because of my own experience in other projects prior to iOS 5. My question is:
How can I ensure that viewDidLoad: is call after initWithFilename: and not between initWithFilename: and initWithNibNameOrNil:bundle:?
If that's not possible, how can I implement an initializer method that receives custom data to create and customize the view?
Thanks!
I have found the problem.
WallpaperDetailsViewController does not inherit directly from UIViewController, but from another custom UIViewController I have implemented.
And what was the problem? That I have initialized a subview in the parent's initWithNibName method, instead of following the lazy-load technique and doing it in viewDidLoad. When WallpaperDetailsViewController was calling its parent initializer it got messy and cause viewDidLoad not to behave properly.
The solution? I moved every subview initialization in the parent class to its viewDidLoad method, and keep my original implementation of WallpaperDetailsViewController intact. Now everything is working as expected
Thanks to #Josh Caswell and #logancautrell
You don't need that empty implementation of initWithNibName:bundle:. Furthermore, it looks like your class here is establishing its designated initializer to be initWithFilename: If that's true, initWithFilename: should be calling the superclass's D.I.:
- (id) initWithFilename:(NSString *)aFilename {
// Call super's designated initializer
self = [super initWithNibName:#"WallpaperDetailsViewController"
bundle:nil];
if ( self ) {
self.filename = aFilename;
}
return self;
}
The rule is that all initializers within a class should call the class's D.I., and the D.I. should itself call the superclass's D.I.
It's not completely clear from what you've posted why loadView: is being called before your initializer has completed. Logancautrell's comment suggesting setting breakpoints in the view loading methods is good.
Why don't you just use a custom setter for the filename property that initializes the UIImage every time the filename is set?
Or, alternately, set the UIImage from the filename property in viewWillAppear: instead of viewDidLoad.
First, it is not recommended that you use dot syntax within your initializer. See the following for some good discussion:
Objective-C Dot Syntax and Init
Second, what you could do is assign the image in your initializer as well. So you could do something along the lines of
- (id) initWithFilename:(NSString *)aFilename {
self = [self initWithNibName:#"WallpaperDetailsViewController" bundle:nil];
if ( self ) {
filename = [aFilename retain];
wallpaper = [[UIImageView alloc] initWithImage:[UIImage imageNamed:aFileName]];
}
return self;
}
This will allow you to get everything setup and in good shape before viewDidLoad is called.
Good Luck!