It might be helpful to know that I come from a iOS background and that this is one of my first OS X projects.
In my project, I have a NSView subclass and a NSViewController and load the view in the controller.
- (void)loadView
{
StartView *view = [StartView alloc] initWithFrame:frame]; // frame exists
self.view = view;
}
The view has a NSButton-property btnNewthat I add in initWithFrame:.
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.btnNew = [[NSButton alloc] initWithFrame:NSRectFromCGRect(CGRectMake(350, 180, 300, 60))];
[self addSubview:self.btnNew];
}
return self;
}
In loadView: I try to set the action and target.
- (void)loadView
{
StartView *view = [StartView alloc] initWithFrame:frame]; // frame exists
[view.btnNew setTarget:self];
[view.btnNew setAction:#selector(createNew:)];
self.view = view;
}
The createNew: function exists, off course.
However, when clicking the button, the app crashes. When I move the setTarget: and the setAction: into the view and perform them on self.btnNew, it works, but I think that's not how you should work with events and user interaction in a decent MVC-architecture.
The problem is with the self in [view.btnNew setTarget:self];, I guess. If I grab the target with view.btnNew.target, this is a nil object (0x000…000). Self exists & view.btnNew exists.
Am I doing something wrong? Or should I really listen to the click in the view and work with a delegate or a notification (I guess not?)
I found it out myself, with help from Niel Deckx.
The problem was ARC. The ViewController got dealloced before the click happened, which explains the NSObject does not respond to selector: the target doesn't exist anymore.
The easy solution is to have the ViewController as a (strong?) property where you create it.
Related
I am working on a project for iOS 7.0+ with a storyboard, using Size Classes with AutoLayout and I'm using a UIView subclass backed by a xib file of the same name.
What I'am trying to do is I'am instantiating a UIView from xib programmatically and adding it to a ViewController from a Storyboard. This ViewController has AutoLayout up and running but the UIView I am adding doesn't respect the frame of the ViewController.
I'm instantiating my UIView subclass like this:
tabBarView = [[SHDTabBarView alloc] initWithFrame:CGRectMake(0, self.view.height-50, self.view.width, 50)];
[self.view addSubview:tabBarView];
And inside the subclass I'm using a set up of creating a UIView IBOutlet called container to instantiate it form code like this:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(void)initalizeSubviews{
NSString *nibName = NSStringFromClass([self class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
[nib instantiateWithOwner:self options:nil];
//Add the view loaded from the nib into self.
[self addSubview:self.container];
}
This is how my xib looks in the Interface Builder (notice the width of the canvas is 320 px):
And that's how it looks on the iPhone 6 (notice how it's getting cut off from the right side):
I've tried to use a multitude of solutions, including doing it all in code with an open-source solution PureLayout, using a manual constraint set up, etc.
None of my findings seem to work right. Ideally, I want to set up everything in Interface Builder, then just add the view to the superview of the ViewController with according frame and let AutoLayout do its magic.
How should I approach this task? Any advices are more than welcome.
Try to set the frame of your subview in the viewDidLayoutSubviews(). Looks like you init your subview before view fully layouted
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've got a QuickLook view that I view some of my app's documents in. It works fine, but I'm having my share of trouble closing the view again. How do I create a touch event / gesture recognizer for which I can detect when the user wants to close the view?
I tried the following, but no events seem to trigger when I test it.
/------------------------ [ TouchPreviewController.h ]---------------------------
#import <Quicklook/Quicklook.h>
#interface TouchPreviewController : QLPreviewController
#end
//------------------------ [ TouchPreviewController.m ]---------------------------
#import "TouchPreviewController.h"
#implementation TouchPreviewController
- (id)init:(CGRect)aRect {
if (self = [super init]) {
// We set it here directly for convenience
// As by default for a UIImageView it is set to NO
UITapGestureRecognizer *singleFingerDTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleSingleDoubleTap:)];
singleFingerDTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:singleFingerDTap];
[self.view setUserInteractionEnabled:YES];
[self.view setMultipleTouchEnabled:YES];
//[singleFingerDTap release];
}
return self;
}
- (IBAction)handleSingleDoubleTap:(UIGestureRecognizer *) sender {
CGPoint tapPoint = [sender locationInView:sender.view.superview];
[UIView beginAnimations:nil context:NULL];
sender.view.center = tapPoint;
[UIView commitAnimations];
NSLog(#"TouchPreviewController tap!" ) ;
}
// I also tried adding this
- (BOOL)gestureRecognizer:(UIGestureRecognizer *) gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*) otherGestureRecognizer {
return YES;
}
#end
Edit: For clarification, this is how I instantiate the controller:
documents = [[NSArray alloc] initWithObjects: filename , nil ] ;
preview = [[TouchPreviewController alloc] init];
preview.dataSource = self;
preview.delegate = self;
//set the frame from the parent view
CGFloat w= backgroundViewHolder.frame.size.width;
CGFloat h= backgroundViewHolder.frame.size.height;
preview.view.frame = CGRectMake(0, 0,w, h);
//refresh the preview controller
[preview reloadData];
[[preview view] setNeedsLayout];
[[preview view] setNeedsDisplay];
[preview refreshCurrentPreviewItem];
//add it
[quickLookView addSubview:preview.view];
Also, I've defined the callback methods as this:
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller
{
return [documents count];
}
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index
{
return [NSURL fileURLWithPath:[documents objectAtIndex:index]];
}
Edit2: One thing i noticed. If I try making swiping gestures, I get the following message. This could shed some light on what is wrong/missing?
Ignoring call to [UIPanGestureRecognizer setTranslation:inView:] since
gesture recognizer is not active.
I think your example code is incomplete. It isn't clear how you are instantiating the TouchPreviewController (storyboard, nib file or loadView.)
I have never used the class so I could be way out in left field.
If you've already instantiated a UITapGestureRecognizer in the parent viewController, it is absorbing the tap events and they aren't passed on to your TouchPreviewController.
I would implement the view hierarchy differently by attaching the UITapGestureRecognizer to the parent viewController and handle presentation and unloading of the QLPreviewController there.
I think you might not have to subclass QLPreviewController by instantiating the viewController from a nib file.
When your parent viewController's UITapGestureRecognizer got an event you would either push the QLPreviewController on the navigation stack or pop it off the navigation stack when done.
Hope this is of some help.
i started a single view template in Xcode 4.2(recently upgraded to Xcode 4.2 and ios5)
so now i have only one view controller.
I added a new class to the project which is a subclass of UIViewcontroller.
Now in the main controller class viewdidLoad method
- (void)viewDidLoad
{
// Override point for customization after application launch.
[super viewDidLoad];
[self presentQuizcontroller];
}
-(void) presentQuizcontroller
{
_QuizController = [[[Quiz alloc] initWithNibName:#"Quiz" bundle:nil] autorelease];
_QuizController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:_QuizController animated:YES]; // Do any additional setup after loading the view, typically from a nib.
}
the problem is in my Quiz class
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
the initWithNibName method does get called(i checked by using breakpoint) but it doesn't passes the condition of if(self) . and hence the view don't appears.
Any ideas?
Edit
After the first answer i tried this way too
- (void)viewDidLoad
{
// Override point for customization after application launch.
[super viewDidLoad];
}
-(void) presentQuizcontroller
{
_QuizController = [[[Quiz alloc] initWithNibName:#"Quiz" bundle:nil] autorelease];
_QuizController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:_QuizController animated:YES]; // Do any additional setup after loading the view, typically from a nib.
}
-(void) awakeFromNib
{
[self presentQuizcontroller];
}
same thing Quiz.m initwithnib name method does not passes the condition if(self).
I think you need to use awakefromnib.
Here is the Link for another StackOverflow post if you want to read more.
PresentModalViewController is depreciated, I think you should now use presentViewController instead of it.
Are you sure that "Quiz" is the name of your file? That string should be same as the name of your xib file, namely something like "QuizController" or "QuizViewController"
Make sure the xib file is properly connected to header/implementation files by checking:
Owner of the xib file should be set as the viewController
View on the xib file (the one above all if you have multiple views) should be connected to viewControllers view.
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