UISegmentedControl does not respect divider images set for UIControlStateDisabled - ios

I'm using the new UIAppearance API in iOS 5 to style a UISegmentedControl with custom graphics. I need to be able to set some segments to be disabled at times during execution, but the UIAppearance methods don't seem to allow me to set a divider image for the UIControlStateDisabled state.
I'm calling:
[[UISegmentedControl appearance] setDividerImage:disabledSelectedImage
forLeftSegmentState:UIControlStateDisabled
rightSegmentState:UIControlStateSelected
barMetrics:UIBarMetricsDefault];
where disabledSelectedImage is a resizable image from this resource:
Yet when I set the left segment to be disabled ([UISegmentedControl setEnabled:forSegmentAtIndex:]), the result is this:
You can clearly see that the UISegmentedControl has defaulted to use the UIControlStateNormal-UIControlStateNormal divider image.
It seems perfectly happy for me to set a background image using UIControlStateDisabled
[[UISegmentedControl appearance] setBackgroundImage:disabledImage
forState:UIControlStateDisabled
barMetrics:UIBarMetricsDefault];
(and respects the image I supply while in the disabled state) but not a divider image. Has anyone come across this or found a solution?

I've decided that this must be an iOS bug and have filed a radar with Apple. My solution to the problem for now is to remove segments, rather than disabling them.

A bit of an ugly workaround but i managed to fix it with the following until apple fixes it itself.
First you need to subclass UISegmentedControl and add the following:
#implementation MJSegmentedControl
- (void)layoutSubviews
{
[super layoutSubviews];
NSInteger cachedIndex = self.selectedSegmentIndex;
self.selectedSegmentIndex = 0;
self.selectedSegmentIndex = cachedIndex;
}
#end

I haven't had a need to use the appearance controls of iOS 5 yet but if all else fails you could add the resizable image as a child of the segmented control to cover up the ugliness. It's a hack, but it may work and will be relatively forwards-compatibile. Just be certain to set the autosizing masks appropriately.

I had the same issue and it really seems to be a bug. However I've found a solution (a workaround).
I've used XIB file with a controller. In XIB file segmented control was just placed and all of the customisations were done in -viewDidLoad method.
Then I've created a UIView subclass which represented entire view in the XIB. It made possible moving all view customisation code to the -awakeFromNib method of this UIView subclass. After moving this code the divider images were set properly.

As suggested by Fernando in this thread:
Customizing UISegmentedControl in iOS 5
You can try to dispatch your UISegmentedControl settings on the main queue via:
dispatch_async(dispatch_get_main_queue(),^{
// disable part of the segmented control
[self.eventScopeSegmentedControl setEnabled:NO forSegmentAtIndex:2];
});
I did this in viewDidLoad and it worked fine for a while but when my app is really busy at startup, this doesn't always work. I'm guessing there's a race condition that may still revert any settings you made when the appearance proxy goes to work.
I added another ugly hack to make this call in viewWillAppear (after the call to super:viewWillAppear) with a flag (set from viewWillLoad) to ensure this only runs once.

There is actually a pretty simple way to get this done. The current behavior is obviously a bug so this is not an ideal solution but simply a workaround that works beautifully. Namely, use an additional UIView as a "disabled visual cue".
The general steps:
Add a UIView as a sibling to the UISegmentedControl. Ensure the UIView is in front of the UISegmentedControl
Apply the desired color and a transparency to the UIView to match your app skin
Move the UIView to be exactly on top of the UISegmentedControl
Shape the UIView to have the exact size top of the UISegmentedControl
Apply a rounded corner to the UIView to mirror the exact shape of the UISegmentedControl
When the UISegmentedControl is supposed to be disabled, simply show the UIView and disable the user interaction on the UISegmentedControl.
When the UISegmentedControl is supposed to be enabled, simply hide the UIView and enable the user interaction on the UISegmentedControl.
In both cases do not change the UISegmentedControl.enabled property.
Note that it seems like a lot of steps but all of this can be coded in so to add support for disabling your custom UISegmentedControl becomes pretty much a 1 liner after you add this to a configure segmented control method.
Here is how my custom segmented control looks when applying this solution:
Enabled Segmented Control
"Disabled" Segmented Control
Here are some code snippets of interest:
Shape the UIView to match the UISegementedControl (load time configuration)
UISegmentedControl* segmentedControl = ...
//Segmented Control disabled visual cue view
UIView* view = ...
//Step #2
view.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6];
//Step #3 and #4
view.frame = segmentedControl.frame;
//Step #5
view.layer.cornerRadius = 5
view.clipsToBounds = YES;
//Ensure this is disabled by default
view.userInteractionEnabled = NO;
Enable/"Disable" UISegementedControl (runtime state change)
BOOL segmentedControlEnabled = ...
if(segmentedControlEnabled) {
segmentedControl.userInteractionEnabled = YES;
view.hidden = YES;
} else {
segmentedControl.userInteractionEnabled = NO;
view.hidden = NO;
}
That's it.
-

Related

custom UIcollectionViewCell, using view to control color changes

I was going thru the tutorial here: http://pinkstone.co.uk/how-to-build-a-uicollectionview-in-ios-8/
And saw a part that looked great because it makes something simple, use of views to show color changes of selected/unselected items.-
(void)awakeFromNib {
// background color
UIView *bgView = [[UIView alloc]initWithFrame:self.bounds];
self.backgroundView = bgView;
self.backgroundView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"blue"]];
// selected background
UIView *selectedView = [[UIView alloc]initWithFrame:self.bounds];
self.selectedBackgroundView = selectedView;
self.selectedBackgroundView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"pink"]];
}
The author showed that this is a simple approach that eliminates the need to manage the state of the cells.
Q. I want to change the color using this method during the highlight process. However, didHighlightItemAtIndexPath is in the CollectionViewController and I'd like to have it use the same process.
Basically what it does is change from one color to another based on selected/unselected. I'd like to use the add a color to indicate the in between state of being highlighted/unhighlighted.
I checked all the methods that are in cell that use UIView, and there is nothing for highlighted/unhighlighted.
Any ideas on an approach that has the advantages of using cell methods? Can I call a custom method from the viewcontroller method and load a view there?
You might think of this as a "press and hold" that changes the color when it's being held.
A UIView wouldn't know anything about it's selected or unselected state since it's a very elemental component and not specific to controls that allow selection. The UICollectionViewCell does have a selected property though, so that would be the right place to change these characteristics. Looking at the docs it seems like either tapping into setSelected or selectedBackgroundView would give you a good opportunity to customize the L&F of selected cells. There's also a highlighted state in case you might also be looking for that.

UIRefreshControl tint color doesn't match given color

The refresh color doesn't match the tint color and looks different, i tryied to change tintAdjustmentMode but the result is the same
Just to note, the spinner and text color should be 0x2C76BE
tvc.refreshControl = [UIRefreshControl new];
tvc.refreshControl.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
tvc.refreshControl.tintColor = [UIColor colorWithHex:0x2C76BE];
tvc.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to query spectrum again" attributes:#{NSForegroundColorAttributeName:[UIColor colorWithHex:0x2C76BE]}];
I had a similar problem with UIRefreshControl not displaying the color correctly when the view loads and I call beginRefreshing(). If the user pulls to refresh, the control correctly displays the tintColor I've specified.
First, subclass the refresh control. Then, override the didMoveToWindow method in the subclass. The following code finds the element that's animated to create the spinner and sets its background color.
This code uses an extension to UIView to return all the view's subviews (I used Jon Willis' answer from Swift: Recursively cycle through all subviews to find a specific class and append to an array).
class CustomRefreshControl: UIRefreshControl {
override func didMoveToWindow() {
super.didMoveToWindow()
if let l = getNestedSubviews().first(where: { $0.layer is CAReplicatorLayer }), l.subviews.count > 0 {
l.subviews[0].backgroundColor = UIColor.orange //getNestedSubviews method is an extension of UIView as referenced above
}
}
The spinner has a CAReplicatorLayer whose view contains one subview. That subview is simply a rectangle implements the graphic element for the spinner. It's that graphic element you're coloring.
UIRefreshControl is a buggy class. I noticed that placing the tvc.refreshControl.tintColor = [UIColor colorWithHex:0x2C76BE]; inside an animation block (even of zero duration) would yield the expected the results. So I tested to do this hideous 'hack': dispatch_async(mainQueue, <#set tintColor#>); and that also give the right result. There might also be a dependency of the refreshcontrol on the the timing of calling -beginRefreshing or -endRefreshing too.
Because I was annoyed so much by UIRefreshControl's buggyness and limitation of only being usable in a UITableViewController, I created a fully customizable one of my own, usable with any type of UIScrollView (UICollectionView, UITableView). Note that I created this before UICollectionViewFlowLayout supported the sticky headers like a tableView, so my refreshcontrol does not work well when that option is on. Feel free to submit a fix ;).
You can find it here https://github.com/Joride/JRTRefreshControl (if this falls under the 'shameless plugging clause' I will remove this link, but I think it is relevant to the question.

Disable UIButton from interaction without changing UIButton look (i.e. greyed out look)

I have the following code where my goal is to disable a UIButton from interaction once it is in the selected mode.
if (...){
cell.requestButton.selected = YES;
cell.requestButton.enabled = NO;
} else {
cell.requestButton.selected = NO;
cell.requestButton.enabled = YES;
}
The side-effect of disabling the UIButton is that it also changes the Button's appearance. Even if I set the same UIImage for both the button's both selected and disabled state, the appearance still takes on a greyed-out looked with a <1 alpha level. Is there a way that I could prevent the disabled state from changing the appearance of the UIButton at all?
Thanks!
Try setting the UIView's userInteractionEnabled property instead. Not sure if that works, but give it a try.
If that doesn't work, you could place a dummy UIView right over it, add constraints to use the original button's position and size, and have it enabled so that it swallows any taps.
Either set the same style as the UIControlStateNormal to the UIControlStateDisabled or have a look at adjustsImageWhenDisabled.

Main UITableVew is visible in searchResultsTableView status bar

I have two UITableViews in my app (one for regular usage and another for searching). When I use regular one the status bar is opaque but when I enter searchResultsTableView I can see some items from regular UITableView.
As you can see the UISearchBar is opaque so there is nothing behind it but status bar is still transculent.
I've already added [self.tableView setHidden:YES]; in searchDisplayController: willShowSearchResultsTableView: but it's a very primitive solution and does not work when my regular UITableView is displayed at the beginning of search.
My question is how to make status bar opaque? Just to avoid this annoying thing.
I've found two solutions to this issue. Both works for me but the second one is simpler and you don't have to do anything in code. I hope you find it useful!
First solution:
You can create a custom UIView in code and add as a subview. If your application has one color of UINavigationBar or any other component and it won't change through the lifecycle you can always implement this in application:didFinishLaunchingWithOption:. Otherwise, you can always add this code in proper moment but you have to remember to add this subview to proper super view. Here is the code sample:
UIView *addStatusBar = [[UIView alloc] init];
addStatusBar.frame = CGRectMake(0, 0, 320, 20);
addStatusBar.backgroundColor = [UIColor colorWithRed:0.79 green:0.79 blue:0.81 alpha:1];
[self.navigationController.view addSubview:addStatusBar];
Second solution:
There is one tiny checkbox in Xcode called Clip Subviews and it's super useful in that case. You can find the view which is visible under Status Bar (In my case it's UITableView) and clip it to subviews. I don't know how but in my case UISearchBar started affecting status bar so now it's opaque qith a color of UISearchBar.

Pattern to fill the entire screen

Every screen of my app has a common tint. Its not a background. Its a pattern that fills the entire screen and it is top of all the views. You can see the pattern flow continuously from one view to another inside the same screen. And it neither obscures other elements nor participate in event handling.
I tried implementing it with this code in my ViewController.
UIColor* texture = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Texture.png"]];
UIView* tintView = [[UIView alloc] initWithFrame:self.view.bounds];
[tintView setBackgroundColor:texture];
[tintView setAlpha:0.5];
[self.view addSubview:tintView];
But it doesn't pass on touches to the views behind it.
tintView shouldn't participate in any event handling. Rather it should let other elements behind it, handle the events like they do it normally.
Other way of doing it is set this as a background of the view property of a UIViewController and set a common alpha for all other subviews of view to show the pattern behind. That will be redundant in most ways.
Any better way of doing this?
Make your tintView a subclass of UIView and implement the hitTest:withEvent: method, returning nil. This will make your view transparent to touches. Or set userInteractionEnabled to NO.
Set the background color with a Textured image
UIImage *bgimg = [UIImage imageNamed:#"Texture.png"];
self.view.backgroundColor = [UIColor colorWithPatternImage:bgimg];

Resources