Our application has a "dark" palette, with mostly black or charcoal backgrounds. This is creating a major problem in Apple controls that ignore (or don't even offer) control over text and background color.
UISegmentedControl is a particularly good example. It's drawn with often illegible, seemingly arbitrary text/background combinations. These controls are all set up with the exact same properties in IB, and yet you never know if they'll be legible from one view controller to the next.
Most of these are OK in "dark" mode in our app, but "light" mode is shambolic. I've spent a day experimenting with themes, UIAppearance, and setting appearance in IB and programmatically. I'm fed up with it. Does anyone actually know how to guarantee legibility in these things?
Try this,
let seg:UISegmentedControl = {
let seg = UISegmentedControl()
seg.insertSegment(withTitle: "tab 1", at: 0, animated: true)
seg.insertSegment(withTitle: "tab 2", at: 1, animated: true)
seg.selectedSegmentTintColor = .red //you can replace the colours you want
seg.backgroundColor = .lightGray //you can replace the colours you want
return seg
}()
Result
Dark Mode
Light Mode
Related
I'm trying to implement UIButton.Configuration to design my buttons in iOS 15 and I'm running into an issue trying to set the background color for the highlighted state. I'm using the UIButton.ConfigurationUpdateHandler, which successfully changes the background color on state changes:
let handler: UIButton.ConfigurationUpdateHandler = { button in
switch button.state {
case [.selected, .highlighted]:
button.configuration?.baseBackgroundColor = .buttonHighlightColor
case .selected:
button.configuration?.baseBackgroundColor = .buttonHighlightColor
case .highlighted:
button.configuration?.baseBackgroundColor = .buttonHighlightColor
case .disabled:
button.configuration?.baseBackgroundColor = .buttonDisabledColor
default:
button.configuration?.baseBackgroundColor = .buttonDefaultColor
}
}
While the background color changes, it seems there is still some sort of white semi-transparent overlay on top of the button when highlighted:
Left is what the button looks like when highlighted now, right is what the highlight color should look like.
I first thought this might be because the button type is System but even with Custom the problem persists. Never had this issue before using UIButton.Configuration. Any ideas?
I have just found the solution, in case someone else has this issue:
Instead of button.configuration?.baseBackgroundColor I had to use button.configuration?.background.backgroundColor and the white overlay is gone.
How can I change the color of the curve that follows a UISwitch control? I tried changing the borderColor but that's tied to the frame.
This is a sample photo from another question. I want to toggle the color of the silver curve in this photo (not the round button and not the orange background)
I tried these 2 properties but the .layer.borderWidth = 2.0 and .layer.cornerRadius = 15 changes the frame and not the curve
When it's on I want the curve to be .clear and when it's off I want it to be .red
lazy var switchControl: UISwitch = {
let switchControl = UISwitch()
switchControl.addTarget(self, action: #selector(switchValueDidChange(_:)), for: .valueChanged)
}()
#objc func switchValueDidChange(_ sender: UISwitch) {
if (sender.isOn == true) {
sender.layer.borderColor = UIColor.clear.cgColor // this doesn't work
} else {
sender.layer.borderColor = UIColor.red.cgColor // this doesn't work
}
}
Yes as iChirag said this can not be done as of now using the built-in UISwitch.
As to why it's not possible, it's hard to say with certainty. My guess would be that it's because Apple encourages using a limited color palette in their Human Interface Guidelines. The main goal of using colors in an iOS app is basically to communicate information, help the user differentiate separate parts of the UI at a glance or to call attention to user actions. Maybe they think that setting a custom color for the curve of a UISwitch is outside of these purposes.
With that said, it's fairly easy to build your own custom UISwitch if you need this appearance. Here is a tutorial to get you started, hope this helps:
Make custom UISwitch (Part 1)
Change Tint color of UISwitch
mSwitch.tintColor = offColor// Custom color
I believed this doesn't possible without some hacking tips of SDK. But I am proposing to create your own such control using an image.
iOS 11 introduces the option for larger text in the Navigation Bar. I would like to have a title that uses multiple colors. For example:
It's fairly easy to set the title, and even to change the color of the entire title:
[[self navigationItem] setTitle: #"Colors"];
[[[self navigationController] navigationBar] setLargeTitleTextAttributes: #{NSForegroundColorAttributeName: [UIColor colorFromHex: redColor]}];
What I can't figure out is how to change just part of the title. For example, a way to select the range like this – NSRangeMake(0, 1) – so that I could apply a color to it.
This must be possible, right?
There is no public API to set your own attributed text for the large title.
The solution is to navigate down the view hierarchy. You specifically mentioned you wanted to avoid this, but it's the only way to modify the colors while getting the rest of the UINavigationBar behavior for free.
Of course, you can always create your own UILabel and set its attributedText, but you will have to re-create any navigation bar animations and other behavior yourself.
Honestly the simplest solution is to modify your design so it doesn't require a multi-colored large title, as this is currently not supported.
I took a dive down the "spelunking" path, and there are a variety of visual issues with animations snapping back to the original text color.
Here is the code I used if it's useful for anyone trying to achieve a similar effect:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
applyColorStyle(toLabels: findTitleLabels())
}
private func applyColorStyle(toLabels labels: [UILabel]) {
for titleLabel in labels {
let attributedString = NSMutableAttributedString(string: titleLabel.text ?? "")
let fullRange = NSRange(location: 0, length: attributedString.length)
attributedString.addAttribute(NSAttributedStringKey.font, value: titleLabel.font, range: fullRange)
let colors = [UIColor.red, UIColor.orange, UIColor.yellow, UIColor.green, UIColor.blue, UIColor.purple]
for (index, color) in colors.enumerated() {
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: NSRange(location: index, length: 1))
}
titleLabel.attributedText = attributedString
}
}
private func findTitleLabels() -> [UILabel] {
guard let navigationController = navigationController else { return [] }
var labels = [UILabel]()
for view in navigationController.navigationBar.subviews {
for subview in view.subviews {
if let label = subview as? UILabel {
if label.text == title { labels.append(label) }
}
}
}
return labels
}
The downside of the "spelunking" approach is it's not a supported API, meaning that it could easily break in a future update or not work as intended in various edge cases.
That's no easy feat unfortunately, it's not officially supported. Here's a few ways I'd consider:
• Override layoutSubiews() in a navigation bar subclass. Traverse the view hierarchy, and mess with the UILabel to apply attributes. I wouldn't recommend this, I've moved away from doing something similar - the iOS 11 navigation bar has a seemingly complex and unpredictable behavior. Things like interactive gestures don't always play nice with whatever you tack on. Your case is a little more simple than mine was though, so there's a chance this may work fine.
• Create your own navigation bar style view from scratch - one that doesn't inherit from UINavigationBar. This is the route I ended up taking when doing something similar. It's more work, and won't mirror future changes Apple make to the visual style - but on the other hand, your app will look the same even across older iOS versions, and you have complete control.
• Hide the nav bar separator, revert it to a non-large size, and place your own view underneath it mimicking the look (the background is a visual effect view with a .extraLight blur style) - you can place a custom label inside this view.
I've been trying to use the appearance proxy API to apply some default colors to some controls, but I've run into a problem.
When I apply a tint color to UISegmentedControl using something like...
UISegmentedControl.appearance().tintColor = UIColor.red
It generates this...
All good, but when I add...
UIImageView.appearance().tintColor = UIColor.green
it changes to...
Just to be clear, I have BOTH this lines in my code
UISegmentedControl.appearance().tintColor = UIColor.red
UIImageView.appearance().tintColor = UIColor.green
It doesn't matter in what order I call them, the result is the same, the UIImageView properties override the UISegmentedControls
I've spent over half a day trying to find a solution to this problem but can't seem to find anything that works.
Running Xcode 8.2, iOS 10, Swift 3
What am I doing wrong and how can I fix?
I am not sure about this, but I guess, UISegmentedControl uses UIImageView to create segments, i.e. the segments we see inside segmented control are UIImageViews and not UIViews. UISegmentedControl even has methods to setImage for a particular segment.
If above is true, we can use appearanceWhenContainedIn API of UIAppearance to set image view tint colour like this:
UIImageView.appearance(whenContainedInInstancesOf: [UISegmentedControl.self]).tintColor = UIColor.red
UIImageView.appearance().tintColor = UIColor.green
I can change the tintColor for my App (everywhere) in a setting pane.
When I change the tintColor, and after that try to return to the default tint color, some buttons in NavBar don't return to blue, how can I handle this?
To verify:
- create a new project Master/Detail
- In the detail view: add Two buttons, named: "Red Interface" and "Blue interface"
- In DetailViewController, add the actions
#IBAction func tapRed(sender: AnyObject) {
view.window!.tintColor = UIColor.redColor()
}
#IBAction func tapBlue(sender: AnyObject) {
view.window!.tintColor = nil
}
Now run the application, create a timestamp, go to detail, tap redInterface, then blue interface
That's OK in the Detail View, but when you return to Master, the "+" button item is red, not blue.
I can fix the problem by setting a real blue color instead of nil, but that's not a long term solution, if Apple change the default tintColor.
Is this a bug? Is there something I can do to bypass this problem?
A quick workaround would be to fetch the default tint color once on the initial app load and store it somewhere (i.e. in the user defaults)
From Apple's iOS 7 UI Transition Guide (Specifically under the Using Tint Color section).
By default, a view’s tint color is nil, which means that the view uses its parent’s tint. It also means that when you ask a view for its tint color, it always returns a color value, even if you haven’t set one.
That's a good workaround!
In fact, I don't even need to store the color.
If I change to
#IBAction func tapBlue(sender: AnyObject) {
view.window!.tintColor = UIButton(type:UIButtonType.System).titleColorForState(.Normal)
}
That gives me the correct blue color, even if I changed the tintColor (probably because I don't address a button in the view hierarchy)