How to Subclass UICollectionViewFlowLayout with Storyboard - objective-c

I'm using a UICollectionView with Storyboard and trying to subclass the UICollectionViewFlowLayout but it doesn't seem to work.
I've created the subclass CollectionViewFlowLayout :
#import "CollectionViewFlowLayout.h"
#implementation CollectionViewFlowLayout
-(id)init
{
NSLog(#"Init of CollectionViewFlowLayout");
if (!(self = [super init])) return nil;
self.itemSize = CGSizeMake(250, 250);
return self;
}
#end
And in the Storyboard's Identity Inspector I changed the class for the flow layout:
But when I save/build/run, the itemSize is not set at 250 and my NSLog isn't being output.
I've seen in examples such as this that you can set the layout in the collectionView controller, but I sort of assumed that wasn't necessary if you set it in the storyboard.

Objects loaded from the storyboard use initWithCoder:, not init. Move your setup code there instead, or have a common method that is called from each initialiser.

Related

Setting up buttons in interface builder but want to call custom init method

I have a custom UIButton class. I have set up the buttons in the interface builder. I have this code:
#import "FriendButton.h"
#implementation FriendButton
-(id)init {
self = [super init];
NSLog(#"init called");
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(longPress:)];
[self addGestureRecognizer:longPress];
return self;
}
It isn't being called though, I have set the class type to FriendButton in the interface builder. Is there a different method I need to use? I know that for custom UITableViewCells one has to use awakeFromNib.
From Apple docs:
Objects that conform to the NSCoding protocol (including all
subclasses of UIView and UIViewController) are initialized using their
initWithCoder: method.

Unable to set content in NSPopover

I'm showing an NSPopover in an NSView, originating from a point on an NSBezierPath. I'm able to show the popover without a problem, but I can't seem to set the string value of the two text fields in it. The popover and the content view are both a custom subclass of NSPopover and NSViewController, respectively. The NSPopover subclass is also the NSPopover's delegate, although I don't implement any delegate methods, so I'm not sure I even need to do that.
Here is my subclass of NSViewController:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverViewController : NSViewController
#end
#import "WeightPopoverViewController.h"
#interface WeightPopoverViewController ()
#end
#implementation WeightPopoverViewController
- (id)init {
self = [super initWithNibName:#"WeightPopoverViewController" bundle:nil];
if (self) {
}
return self;
}
#end
And my subclass of NSPopover:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverController : NSPopover <NSPopoverDelegate> {
NSTextField *dateLabel;
NSTextField *weightLabel;
}
#property (strong) IBOutlet NSTextField *dateLabel;
#property (strong) IBOutlet NSTextField *weightLabel;
#end
#import "WeightPopoverController.h"
#implementation WeightPopoverController
#synthesize weightLabel;
#synthesize dateLabel;
#end
This is the code in my NSView subclass that opens up the popover:
#interface WeightGraphViewController () {
WeightPopoverController *popover;
WeightPopoverViewController *vc;
}
...
-(void)mouseEntered:(NSEvent *)theEvent {
// initialize the popover and its view controller
vc = [[WeightPopoverViewController alloc] init];
popover = [[WeightPopoverController alloc] init];
// configure popover
[popover setContentViewController:vc];
[popover setDelegate:popover];
[popover setAnimates:NO];
// set labels
for (id key in (id)[theEvent userData]) {
[popover.weightLabel setStringValue:[(NSDictionary*)[theEvent userData] objectForKey:key]];
[popover.dateLabel setStringValue:key];
}
// set the location
(redacted, irrelevant)
// show popover
[popover showRelativeToRect:rect ofView:[self window].contentView preferredEdge:NSMaxYEdge];
}
-(void)mouseExited:(NSEvent *)theEvent {
[popover close];
popover = nil;
}
In WeightPopoverViewController.xib, I've set the File's Owner to WeightPopoverViewController and connected the view to the custom NSView. In this xib I also have an Object set to WeightPopoverController with the dateLabel and weightLabel connected to their text fields and the contentViewController set to File's Owner.
I think where I am going wrong is likely related to how I have configured my class / instance variables for the NSPopover, but from the research I've done and documentation I've read I can't seem to crack where I've gone wrong. Any help would be appreciated.
UPDATE:
I removed the NSPopover subclass from code and from IB. I put my outlets in my NSViewController and connected them in IB. However, I'm still not able to set the string values. The following won't compile with the error "Property 'weightLabel' not found on object of type NSPopover*'".
#interface WeightGraphViewController () {
NSPopover *popover;
...
}
-(void)mouseEntered:(NSEvent *)theEvent {
vc = [[WeightPopoverViewController alloc] init];
popover = [[NSPopover alloc] init];
[popover setContentViewController:vc];
[popover.dateLabel setStringValue:#"test"];
}
I have the property definition exactly as I had it in my NSPopover subclass, but now in my NSViewController. This is actually what I had before, and since I wasn't able to set the properties from the NSViewController, I figured I needed to do it through a subclass of NSPopover. This is why I thought I am having an issue with how I have configured my class / instance variables.
You seem to be creating two popovers, one in code (popover = [[WeightPopoverController alloc] init]) and one in Interface Builder (In this xib I also have an Object set to WeightPopoverController). Have a think about what you’re trying to achieve.
I would also advise against subclassing NSPopover. I believe this is causing confusion and is unnecessary. Instead, put the outlets to your dateLabel and weightLabel in the popover’s content view controller.
I've experienced something that I think is similar. The root problem is that the "outlets" connecting your view (XIB) to your controller are not initialized until after the view has been displayed. If the controller tries to set properties on any UI controls in the view before the popover has been opened, those changes are ignored (since all the controls will be nil).
Luckily, there's an easy solution (as mentioned in this answer): just invoke the view getter on your controller, and it will force the view to initialize sooner.
In other words:
popover = [NSPopover new];
myController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
popover.contentViewController = myController;
[myController view]; // force view to initialize
...set some values on myController... // works because view is now loaded
[popover showRelativeToRect: ...];

iOS - Create UIView subclass for rounded rectangle

I'm trying to create & use a very simple UIView subclass for a rectangle with rounded corners. I've created a new class as follows :
RoundedRect.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface RoundedRect : UIView
#end
RoundedRect.m
#import "RoundedRect.h"
#implementation RoundedRect
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[self layer] setCornerRadius:10.0f];
[[self layer] setMasksToBounds:YES];
}
return self;
}
#end
I'm using iOS 5.1 with storyboards and have set the custom class property in the IB inspector window to 'RoundedRect', but when I run the app the rectangle still has square corners. Have I missed something obvious?
Thanks
Jonathan
In iOS 5 and up, there is absolutely no need to subclass - you can do it all from Interface Builder.
Select the UIView you want to modify.
Go to the Identity Inspector.
In "User Defined & Runtime Attributes", add "layer.cornerRadius" in Key Path, Type should be "Number" and whatever setting you require.
Also add 'layer.masksToBounds' as Boolean.
Done! With no subclassing, and all in IB.
The other guys have already answered the question but I would refactor it like this to enable use in nibs and in code
#import "RoundedRect.h"
#implementation RoundedRect
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit;
{
CALayer *layer = self.layer;
layer.cornerRadius = 10.0f;
layer.masksToBounds = YES;
}
#end
The initWithFrame method is not called when the view is instantiated from a XIB file. Instead, the initWithCoder: initializer is called, so you need to perform the same initialization in this method.
For views that are loaded from a NIB file, the designated initializer is initWithCoder:. initWithFame: is not called in this case.
If UIView load from Nib, you should use method
- (void)awakeFromNib

iOS Detecting Whether Tab is Changed

I have tabbed iOS application. I need to know which tab is active and detect when tab is changed. In storyboard I have a tab view controller, which changes the view when you click a tab fine. I created a class TabBarController and it is defined as follows:
Header
#interface TabBarController : UITabBarController <UITabBarControllerDelegate>
#end
Implementation
#import "TabBarController.h"
#implementation TabBarController
// In the initialization section, set the delegate
- (id) init
{
self = [super init];
if (self)
{
self.delegate = self;
}
return self;
}
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
}
#end
However, I couldn't detect tab changes with the code above. What do you think that the problem is?
I haven't linked my tab view to any outlets, but segues to other views. Is this the problem? Then, where should I link my outlet to?
Have you confirmed that your init method is being called? I don't think init is the designated initializer for UITabBarController and may not be called when loading the controller from a nib/storyboard.
If that's the case you may find it easier to set the delegate in your viewDidLoad since that will be called regardless of how the object is initialized or else make sure you set the delegate when -initWithNibName:bundle: or -initWithCoder is used to instantiate the object.
Solution to this is the implementation viewDidLoad as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"Tabs showed up!");
self.delegate = self;
}

Can I use segues with designated initializers of view controllers?

I am new to storyboards and I have set up a segue from a button to a view controller. This view controller is of a custom subclass SFListViewController, which has a designated initializer initWithList:.
Using the designated initializer is the only way to correctly initialize the view controller. However, when using segues the designated initializer won't be called (obviously).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Show List"]) {
SFListViewController *listViewController = segue.destinationViewController;
// ????
}
}
How can I make the segue call the designated initializer when performed?
As far as I know this is not possible since the storyboard framework simply calls alloc and init on the class you define in Interfacebuilder. Additionally the segue's destinationViewController attribute is read-only so you couldn't simply replace the existing ViewController either.
The only way to use Storyboarding would probably be to create a wrapper-class that internally instantiates the SFListViewController with the desired attributes and then functions as a proxy object and thus propagates viewDid*** and viewWill***-methods to the wrapped class and also returning the wrapped VC's view in a readonly view property... You get the idea.
Generally there are a number of alternative ways to initialize a UIViewController in such a case:
There is an option to specify "User defined runtime Attributes" which could be used for initialisation.
Override the prepareForSegue: method, like you tried, in your root ViewController and do "post-alloc-init-initialisation" there.
If worst comes worst, you could fall back to an IBAction in order to be able to initialize the ViewController yourself.
I hope this helps.
Edit: I can verify that the Proxy-Approach works since I just came across a similar problem with ABPeoplePickerNavigationController where this approach worked nicely. Since we've set up the thing in our story board please note that you have to use awakeFromNib in order to do initial configuration (instead of some init method).
This is the code for my wrapper class:
#import "PeoplePickerViewControllerWrapper.h"
#implementation PeoplePickerViewControllerWrapper
#synthesize ppvc = _ppvc; // This is the object I'm proxying (The proxyee so to speak)
#synthesize delegate = _delegate;
- (void)awakeFromNib
{
self.ppvc = [[ABPeoplePickerNavigationController alloc] init ];
self.ppvc.peoplePickerDelegate = self;
self.ppvc.addressBook = ABAddressBookCreate();
self.ppvc.displayedProperties = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonPhoneProperty]];
}
#pragma mark - View lifecycle
- (void)loadView
{
[super loadView];
[self.ppvc loadView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.ppvc viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.ppvc viewWillAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.ppvc viewDidDisappear:animated];
}
-(UIView *)view{
return self.ppvc.view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
[self.ppvc viewDidUnload];
}