Why do my UIButtons change color in iOS 7 upon controller transition? - cocoa-touch

So I have some UIButtons. They're blue when enabled, and gray when disabled, as I expect.
If I go from my front page to Current Contests and there's a network error like this:
I call [self.navigationController popViewControllerAnimated:YES]; to get back to the main page, and I get this on iOS 7, iPhone 4:
Gray buttons, but they work. So I know they're not disabled. This does not happen on iOS 8, iPad Mini.

You can fix this using the tintAdjustmentMode property, introduced in iOS 7:
When this property’s value is UIViewTintAdjustmentModeDimmed, the
value of the tintColor property is modified to provide a dimmed
appearance.
If the system cannot find a non-default value in the subview hierarchy
when you query this property, the value is
UIViewTintAdjustmentModeNormal.
When this property’s value changes (either by the view’s value
changing or by one of its superview’s values changing), -the system
calls the tintColorDidChange method to allow the view to refresh its
rendering.
- from the UIView Class Reference.
When certain overlay views such as UIAlertView, etc., become visible, they essentially change this property to UIViewTintAdjustmentModeDimmed. You can fix this by setting the tintAdjustmentMode for the entire UIWindow (and therefore subview hierarchy) with:
self.window.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;

Related

iOS - hidden accessory becomes visible after orientation change

In my case I'm using a UITextField as the accessory that I don't need to show all the time. I've confirmed the change happens after the orientation notification events fire. I guess a hack would be to resize the accessory to zero height, but I'm reticent to do this.
Wondering if anyone has encountered this and found a solution?
Have entered a bug report and provided a sample project. For those with higher privileges, it is searchable on bugreport.apple.com as ID 16771757. I have also copied it to a Dropbox account accessible as https://www.dropbox.com/s/o28vo04ig3yhgz6/ID16771757.zip.
Thank you for reading.
iOS calls such methods for input accessory view instance:
[inputAccessoryView setAlpha:1]; when owner of accessory view becomes first responder (internal method call -[UIPeripheralHost(UIKitInternal) executeTransition:]);
[inputAccessoryView setHidden:NO]; when interface rotation finished (internal method call -[UIPeripheralHost finishRotationOfKeyboard:]);
That's why your input accessory view becomes visible after interface rotation event.
Solution depends on behaviour that you expect:
Let's imagine that input accessory view height = 44 ->
Now you hide input accessory view and set owner as first responder:
If you expect inputAccessoryView.frame.size.height equals 0 then solution for hiding input accessory view is set it to nil: inputAccessoryView = nil;
If you expect inputAccessoryView.frame.size.height equals 44 then solution for hiding input accessory view is override setHidden: method for it:
- (void)setHidden:(BOOL)hidden {
[super setHidden:self.customIsHiddenFlag];
}
where customIsHiddenFlag property that you need use for implementing logic of showing/hiding accessory view;
or override setAlpha: method:
- (void)setAlpha:(CGFloat)alpha {
[super setAlpha:(self.customIsHiddenFlag ? 0 : 1)];
}
These solutions work for iOS 7.
For iOS 6 you could use your base solution inputAccessoryView.hidden = YES and it works because iOS doesn't call setHidden:NO automatically when interface rotation is fired.
It seems that you are right and it's a bug cause of different behaviour on iOS6 and iOS7. If Apple wants to show input accessory view forcedly then they should call setHidden:NO not only after interface rotation but also when owner becomes first responder.
From Apple's documentation on inputAccessoryView:
… Subclasses that want to attach custom controls to either a system-supplied input view (such as the keyboard) or a custom input view (one you provide in the inputView property) should redeclare this property as readwrite and use it to manage their custom accessory view. [emphasis mine]
So the correct way to hide the accessory view would be redeclaring the property as readwrite:
#property (nonatomic, readwrite) UIView *inputAccessoryView;
removing the accessory view from superview and setting the property to nil when appropriate:
- (IBAction)hideAccessoryView:(UIButton *)sender
{
[self.inputAccessoryView removeFromSuperview];
self.inputAccessoryView = nil;
}
This is correct with regard to the docs but if you look at the view hierarchy, there's a UIPeripheralHostView (UIKit private class) that does not change its size. This most likely means that throwing out the accessory view will not be reflected by keyboard size — it'll stay the same. Keep this in mind if you plan to calculate any offsets to adjust to on-screen keyboard.
That said, the best way for you to move forward might be using a completely transparent view as the accessory view and have your custom view (UITextField in this case) as a subview. That way you will get both complete control over your custom view and consistent behaviour of your app on current and future versions of iOS.
Edit:
Here's a screenshot showing a slightly modified version of your bug report app with UIPeripheralHostView highlighted:
You can see how size of the view stays the same after the accessory view has been removed.
When you add an accessory view, you "pass" it to the system for layout. It is more than likely, when Apple performs layout on the keyboard view, it also layouts the accessory view and sets it to visible. Setting the accessory as hidden can also have other side effects, such as the keyboard height being incorrectly calculated, thus causing incorrect inset calculation.
From my experience, it is best to remove the accessory and add it again when necessary. Resizing the accessory view will cause other issues related to keyboard size. If you want to hide and show quickly, subclass the view that includes the accessory view, and implement internally the setting and removing of accessory view.

iOS 7 contentSize not calculated properly

My app does some element placement based on contentSize property. However I have found out that iOS 7 doesn't calculate it properly until viewDidLoad method is called. Setting text in text view is done in viewDidLoad however all positioning is done in viewWillAppear. All of it was working until iOS7. Is there a way to calculate positions of elements before they are displayed to user?
In viewDidLoad, you shouldn't do much geometry stuff.
"You should not initialise UI geometry-related things in viewDidLoad, because the geometry of your view is not set at this point and the results will be unpredictable."
I find a workaround.
You have string, then you can use a NSString method (though deprecated in iOS7) to get it's size, then you have a close height, you can plus 50 or some other value.

Xcode's auto layout is only effective in viewDidAppear and this is very problematic

After upgrading my project to iOS 6, I realized that auto layout is only effective in viewDidAppear and most of my code expects the view's frame to be available in viewDidLoad. This limitation renders the really nice auto layout feature almost useless for me. Is there any suggestions to help me use auto layout?
For example, sometimes the developer needs to adjust information about a subview based on where auto layout chooses to place that particular subview. The subview's final location cannot be ascertained by the developer until AFTER the user has already seen it. The user should not see these information adjustments but be presented the final results all at once.
More specifically: What if I want to change an image in a view based on where auto-layout places that view? I cannot query that location and then change the image without the user seeing that happen.
As a general rule, the views frame/bounds should never be relied on in viewDidLoad.
The viewDidLoad method only gets called once the view has been created either programmatically or via a .nib/.xib file. At this point, the view has not been setup, only loaded into memory.
You should always do your view layout in either viewWillAppear or viewDidAppear as these methods are called once the view has been prepared for presentation.
As a test, if you simply NSLog(#"frame: %#", NSStringFromCGRect(self.view.frame)); in both your viewDidLoad and viewWillAppear methods, you will see that only the latter method returns the actual view size in relation to any other elements wrapped around your view (such as UINavigationBar and UITabBar).
As told by #charshep in a comment, calling view.layoutIfNeeded() in viewWillAppear can do the trick.
Quote of his original comment
I had trouble getting a table view to appear at the correct scroll position when pushing it [...] because layout wasn't occurring until after viewWillAppear. That meant the scroll calculation was occurring before the correct size was set so the result was off. What worked for me was calling layoutIfNeeded followed by the code to set the scroll position in viewWillAppear.

iOS PopOver - only from bar button item or round rect?

Im building an iPad app which has a tableview embeded within a UIview (odd, i know). In the UITableview there is data loaded in via a PLIST. I want to allow a user to "select" a row and it would show a "popover" (uiview) containing a textfield allowing them to edit that field.
However, looking through the code i see there are only two ways to fire the function:
presentPopoverFromBarButtonItem
presentPopoverFromRect
IS this true, can you only fire that function from either a round rect or a bar button item? I have a feeling i can be done, however searching has not turned up any answers.
Yes you can only present a popover from a bar button item or from a rect in a view.
From the official doc:
Presenting and Dismissing the Popover
– presentPopoverFromRect:inView:permittedArrowDirections:animated:
– presentPopoverFromBarButtonItem:permittedArrowDirections:animated:
– dismissPopoverAnimated:
Those are the only two ways to present a subclass of UIPopoverController. It is not clear in your question as to why "presentPopoverFromRect" wouldn't work.
I understand that "presentPopoverFromBarButtonItem" is a bit limiting but "presentPopoverFromRect" lets you do whatever you want. It allows you to position a popover relative to a view where the "rect" provides a way to offset the popover relative to the view's frame.
Try:
CGRect popoverRectFromCell = CGRectMake(tappedCell.frame.size.width / 2, tappedCell.frame.size.height / 2, 1, 1);
[cellPopover presentPopoverFromRect:popoverRectFromCell
inView:tappedCell
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
Of course you can tweak the "popoverRectFromCell" to achieve desired results.
However, I am not sure this is really a use-case for a UIPopoverController. Might be easier to just make a custom view and use that for text input when the user taps a cell.

Odd behavior when showing UIPopoverController

In my iPad app, I save the state (visible/not visible) of a popover. So, during the launch of the app I initialize the UIPopoverController and tell it to show itself by using presentPopoverFromBarButtonItem:permittedArrowDirections:animated:. For the first argument (UIBarButtonItem), I use self.navigationItem.rightBarButtonItem. However, the popover keeps showing up on the left side of the screen (and not underneath the targeted button).
After the app is launched, the behavior is as expected. Any suggestions how to solve this?
For your information, I initialize the rightBarButtonItem and assign it to the navigationItem in the viewDidLoad method and before asking the popover to present itself. I have tried to call the popover in viewWillAppear and viewDidLoad, but the effect is the same.
My best alternative is to use presentPopoverFromRect:inView:permittedArrowDirections:animated: instead and "guess" the position depending on the orientation of the device.
Update: when I rotate the iPad, the popover does jump to the correct position. It seems that the barButtonItem's position is only determined at the last minute and after I ask my popover to present itself.
In situations like these where timing appears to be important I found that the trick of postponing an action until the next iteration of the run loop helps. If developing for iOS 4.0+, this can be easily achieved with GDC:
// call from viewDidAppear:
dispatch_async(dispatch_get_main_queue(), ^{
// call presentPopoverFromBarButtonItem:permittedArrowDirections:animated: here
});