Xcode6, iOS8 and (void)layoutSubviews - uilabel

I have custom UILabel which works fine on iOS6 and iOS7. But on iOS8 this label's (void)layoutSubviews method never get called.
I create this label with initWithFrame, so this method should be called - and it's called on another iOS versions.
What happens with autoLayout system in iOS8?

I just want to add this answer because the question title may lead a lot of ppl here with similar issues (like me).
With iOS 8 to 8.0.2 LayoutSubviews calls are unreliable. They may not be called ever or (in my case) are called in a loop.
Even though you should not do it, it was pretty safe to alloc stuff in LayoutSubviews, but with this buggy (?!) behaviour it can cause hard-to-trace bugs.
I don't know if 8.1 fixes all the issues but it will be some time until customer devices will run 8.1 and they do run 8.0.2 NOW

I had same issue. Really layoutSubviews is not called anymore for UILabel on iOS8, as Apple does not expect anybody uses it as superview.
I am using ReactiveCocoaLayout, so it can be done by subscribing to rcl_frameSignal or rcl_boundsSignal.
-(void)awakeFromNib
{
[ self.rcl_boundsSignal
subscribeNext:
^( NSValue* boundsValue )
{
//layout changed
} ];
}
Or you can use simple KVO to know when frame has been changed:
-(void)dealloc
{
[ self removeObserver: self forKeyPath: #"layer.bounds" ];
}
-(void)observeValueForKeyPath:( NSString* )keyPath
ofObject:( id )object
change:( NSDictionary* )change
context:( void* )context
{
if ( [ keyPath isEqualToString: #"layer.bounds" ] )
{
//layoutSubviews
}
else
{
[ super observeValueForKeyPath: keyPath
ofObject: object
change: change
context: context ];
}
}
-(void)awakeFromNib
{
[ self addObserver: self
forKeyPath: #"layer.bounds"
options: NSKeyValueObservingOptionNew
context: 0 ];
}

The bug was fixed by Apple in iOS 8.1 (beta).

Related

NSView 'window' is deprecated XCode 4

I have installed lately XCode 4+, and since on I get warning message 'window' is deprecated.
I have subclassed NSView and called it CentralView and used it to load various views dynamically. I had to subclass the NSView, because there are methods I needed to override.
In other class that controls loading central views I have created following method:
- (IBAction)showUserInfoView:(id)sender{
NSLog(#"Load new user info page");
// Try to end editing
NSWindow *w = [centralView window]; // Here I get warning 'window' is deprecated
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
// Put the view in the box
NSView *v = [[viewControllers objectAtIndex:0] view];
NSArray* viewSet = [NSArray arrayWithObject: v];
[centralView setSubviews: viewSet];
}
As far as I am aware the window method in NSView is up to date. Why do I get message?
What type is centralView? What happens if you say [(NSView*)centralView window]?
It's possible that the compiler is not finding -[NSView window], but some other -window method which is deprecated. If centralView is declared as an id then the compiler doesn't know it's an NSView and has to guess.

UINavigationBar topItem/items seems to double-pop on back

I am managing my own UINavigationBar. I need to do this due to extensive skinning. The documentation for UINavigationController warns that there are limitations to skinning the UINavigationBar when used with a UINavigationController.
I have put in extensive logging and from everything I can tell, pushing the "Back" button in the UINavigationController pops two items off of of the stack instead of one. I get a single delegate callback telling me that it is removing the logical item, but it actually removes that one and one more.
The item added to the UINavigationController in awakeFromNib should never be removed. It is being removed for some reason.
There are two similar questions, but neither have satisfactory answers. The two questions are:
UINavigationBar .items accessor doesn't return the current UINavigationItem
UINavigationBar seems to pop 2 items off stack on "back"
- (void)awakeFromNib {
[headerView setDelegate: self];
[headerView pushNavigationItem: tableDisplay animated: NO];
}
- (void) selectedStory: (NSNotification *)not {
[headerView pushNavigationItem: base animated: NO];
NSLog(#"Selected story: %#", base);
}
- (void) baseNav {
NSLog(#"Current items: %#", [headerView items]);
BaseInnerItem *current = (BaseInnerItem *)[headerView topItem];
[self addSubview: [current view]];
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPushItem: (UINavigationItem *)item {
return YES;
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPopItem: (UINavigationItem *)item {
return YES;
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item {
NSLog(#"didPushItem: %#", item);
[self baseNav];
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item {
NSLog(#"didPopItem: %#", item);
[self baseNav];
}
Edited to add relevant debugging from a single run:
2010-10-13 02:12:45.911 Remix2[17037:207] didPushItem: <TableDisplay: 0x5d41cc0>
2010-10-13 02:12:45.912 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>"
)
2010-10-13 02:12:49.020 Remix2[17037:207] didPushItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:49.021 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>",
"<WebDisplay: 0x591a590>"
)
2010-10-13 02:12:49.023 Remix2[17037:207] Selected story: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.498 Remix2[17037:207] didPopItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.499 Remix2[17037:207] Current items: (
)
You always have to call [super awakeFromNib] when your subclass implements that method, per the documentation for -awakeFromNib:
You must call the super implementation of awakeFromNib to give parent classes the opportunity to perform any additional initialization they require
Importantly, however, ...
I don't understand why you have to actually manage your own navigation bar. If you subclass UINavigationBar and only override certain drawing or layout methods such as -drawRect:, -layoutSubviews, etc., then all of the logic behind managing the navigation bar in a navigation controller will just fall back on the original UINaviationBar class.
I've had to do extensive view customization for almost every major UIKit class, but I always left the complicated logic to the original classes, overriding only drawing methods to customize the look and feel.
Incidentally, it's actually much easier to skin an entire app without subclassing at all if all you're doing is using custom image assets. By setting a layer's contents property, you can either customize the look and feel of a UIView-based class on an as-needed basis or throughout your entire app:
#import <QuartzCore/QuartzCore.h>
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage * navigationBarContents = [UIImage imageNamed:#"navigation-bar"];
self.navigationController.navigationBar.layer.contents =
(id)navigationBarContents.CGImage;
}
You can set the contents for any class that inherits from UIView: navigation bars, toolbars, buttons, etc. It's a lot easier to manage this way without having to subclass at all.
This appears to be a bug in the implementation of -[UINavigationBar items]
When called from inside the -navigationBar:didPopItem: delegate method, it will omit the last object. You can check this by calling [navigationBar valueForKey:#"_itemStack"] to retrieve the underlying array and see that the expected items are still there.
Adding a dispatch_async inside -navigationBar:didPopItem:method successfully works around the issue in my app.

viewDidLoad in NSViewController?

On the iPhone I use UIViewController's viewDidLoad to run code to set up the view.
How can I do that with NSViewController?
I've tried loadView but it doesn't work...
I figured it out within minutes of posting my comment. Adding my finding as an answer because it is an example which is missing in the docs. The below code will give you the viewDidLoad method that you want. Its so easy in a way that i wonder why Apple has not implemented it yet in OS X.
- (void)viewWillLoad {
if([NSViewController instancesRespondToSelector:#selector(viewWillLoad)]) {
[super viewWillLoad];
}
...
}
- (void)viewDidLoad {
if([NSViewController instancesRespondToSelector:#selector(viewWillLoad)]) {
[super viewDidLoad];
}
}
- (void)loadView {
BOOL ownImp = ![NSViewController instancesRespondToSelector:#selector(viewWillLoad)];
if(ownImp) {
[self viewWillLoad];
}
[super loadView];
if(ownImp) {
[self viewDidLoad];
}
}
Original source: http://www.cocoabuilder.com/archive/cocoa/195802-garbage-collection-leaks-and-drains.html
As of OS X 10.10, viewDidLoad is available and supported on NSViewController.
Prior to that, you had to go by this nugget in Snow Leopards' release notes:
Advice for People who Are Looking for -viewWillLoad and -viewDidLoad Methods in NSViewController
Even though NSWindowController has -windowWillLoad and -windowDidLoad methods for you to override the NSViewController class introduced in Mac OS 10.5 does not have corresponding -viewWillLoad and -viewDidLoad methods. You can override -[NSViewController loadView] to customize what happens immediately before or immediately after nib loading done by a view controller.
As of OSX 10.10 (Yosemite), there is now a -viewDidLoad, -viewWillAppear, -viewDidAppear, -viewWillDisappear in NSViewController. See WWDC 2014 - Storyboards and Controllers on OS X session for more info, to find out when each of them gets called, etc.
Here's the relevant bit from the 10.10 header docs about -viewDidLoad:
Called after the view has been loaded. For view controllers created in
code, this is after -loadView. For view controllers unarchived from a
nib, this is after the view is set. Default does nothing.
- (void)viewDidLoad NS_AVAILABLE_MAC(10_10);
why don't you try this:
- (void)awakeFromNib {
//setup code
NSLog(#"hello there");
}
It looks like in 10.10, viewDidLoad is now in NSViewController.
hmm actually I would also do this...
- (void)viewWillLoad {
if (! bool_viewwillload) {
// execute the code
bool_viewwillload = true;
}
}
- (void)viewDidLoad {
if (! bool_viewdidload) {
// execute the code
bool_viewdidload = true;
}
}
and then just make the load view like this
- (void)loadView {
[self viewWillLoad];
[super loadView];
[self viewDidLoad];
}

Updating UI Elements with Controller Constructor

I have an NSSlider (slider) and an NSLabel (label) on a window. I also have a class "Controller" that updates the label whenever the slider's value is changed.
The default position of the slider is 0.5, I'm trying to get is where Controller's constructor updates the label upon program launch.
The following is the implementation file for my attempt to do this. Everything works fine except the label is always 0 when I start the program.
#implementation Controller
{
}
-(id)init
{
NSLog(#"initializing...");
[self updateLabel];
return self;
}
- (IBAction)sliderChanged:(id)sender
{
[self updateLabel];
}
- (void)updateLabel
{
label.text = [NSString stringWithFormat:#"%.1f", slider.value];
}
#end
In the console I see the text "initializing...", but the label is never updated. What am I missing?
The controller may be getting initialized (where is your call to [super init]?), but that doesn't mean the outlets are hooked up. The proper way to do that would be to rely on a viewDidLoad, windowDidLoad, or awakeFromNib method.
you should achieve this with bindings and without any "glue code" in controllers.
Here's some reference on how to use them: http://cocoadevcentral.com/articles/000080.php

Programmatically attaching event handlers

So, I'm trying to programmatically attach event handlers to widgets I've placed on my iphone application using:
addTarget:action:forControlEvents
I have added a UISegmentedControl in Interface Builder which is exposed through #property seg and in loadView, I have:
- (void)loadView
{
[ super loadView ] ;
//k after that attach our own event handlers
[ seg addTarget:seg action:#selector(sliderEventIB) forControlEvents:UIControlEventAllEvents ];
}
sliderEventIB, just tells us it feels the event:
-(IBAction)sliderEventIB:(id)sender forEvent:(UIEvent*)event
{
puts( "I feel you joanna" ) ;
}
but the error I'm getting is
ViewControllersTest[6744:207] *** -[UISegmentedControl sliderEventIB]:
unrecognized selector sent to instance 0x3b21b30
Any idea what it doesn't like here?
It seems like you just forgot to insert the colon in addTarget:
[ seg addTarget:seg action:#selector(sliderEventIB:) forControlEvents:UIControlEventAllEvents ];
It should be sliderEventIB: not sliderEventIB.
The proper code is as such:
- (void)loadView
{
[super loadView];
[seg addTarget:self action:#selector(sliderEventIB:forEvent:) forControlEvents:UIControlEventAllEvents];
}
- (IBAction)sliderEventIB:(id)sender forEvent:(UIEvent*)event
{
NSLog(#"I feel you joanna");
}
Notice that the method has the same selector as is registered using addTarget:action:forControlEvents.
Well, the UISegmentedControl doesn't have the 'sliderEventIB' method.
The 'addTarget' section of the method asks: "who do I inform once an event occurs?". In this case, you specified that the UISegmentedControl should be informed and it should call sliderEventIB on that object. Instead, you should say
[seg addTarget:self action:#selector(sliderEventIB) forControlEvents: UIControlEventAllEvents]