Question

I know that for elements of classes UIButton and UIBarButtonItem they automatically assume window.tintColor as the main colour, which results of an immediate change in case I set a new tintColor to window at any time in the app.

I was wondering if there is any way to make UILabel elements to follow the same pattern, where once created they automatically assumer its default colour as window.tintColor and if changing window.tintColor at any time within my app runtime would also result in changing the UILabel tintColour automatically?

I hope that makes sense.

Was it helpful?

Solution

UILabels are a subclass of UIView, so when you are running in iOS 7 they will have a tintColor property and will inherit that color from their parent view if their tint color is set to nil (which is default).

From Apple's Documentation:

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.

However, you also ask "if changing window.tintColor at any time within my app runtime would also result in changing the UILabel tintColour automatically?" Apple advises you to not change the tint color when items are on screen:

In general, it’s best to change a view’s tint color while the view is offscreen.

I would guess this is because there is no guarentee that all the various UI elements will detect the tintColor change and update their visible views. However, the UIView documentation suggests a workaround if you want to update tintColor while your UILables are on screen:

To refresh subview rendering when this property changes, override the tintColorDidChange method.

So just make sure to call tintColorDidChange on any views currently on screen whose tint color should update when the tintColor of their parent view changes.

But why don't your UILabel's update their color?

So the above helps you set and update your various tintColor's, but you're not seeing any effect - why?

Well that has to do with what Apple designed Tint to indicate. From the Human Interface Guidelines:

color gives users a strong visual indicator of interactivity

Apple got rid of borders and gradients around interactive elements and replaced them with color - specifically tintColor. The whole idea behind tintColor is that things users can tap on get it, and things they can't tap on don't.

UILabel is not an interactive element - it is a text description - and so Apple will let you set a tintColor on it (as any UIView has a tintColor) but setting that tintColor will not change how it is drawn.

So what should you do? First, be aware that making more than just buttons take on the tint color could be a poor UI choice for your app - iOS 7 users and Apple app reviewers both will be expecting those rules to be followed.

So are you forced to keep your UILabel free from color then?

No - especially if you do things "right". Apple explains:

In a content area, add a button border or background only if necessary. Buttons in bars, action sheets, and alerts don’t need borders because users know that most of the items in these areas are interactive. In a content area, on the other hand, a button might need a border or a background to distinguish it from the rest of the content.

I would suggest you consider the UI of your app. If you really want your non-intereactive elements to have the same tintColor as your interactive elements, then make sure you use something more, like a border or background color, so your users (and Apple app reviewers) know what is clickable and what is not.

As to how you should update the colors, you can either manually set the textColor property to be whatever color you want, or you'll need to make your own subclass of UILabel that overrides - (void)tintColorDidChange to update the textColor property when notifications are sent out - allowing you to have a UILabel whose text updates to match the tintColor of its parent.

I hope this helps!

OTHER TIPS

Found above explanation to be helpful - particularly the pointer to tintColorDidChange(). However, getting it to work out right didn't work at first, but finally came up with a code example that does. Basically, I had a tableView with cells containing images and labels. The images would update with a change in tintColor, but not the labels - which didn't look right. Better for either both to change or neither change. The code below is for the cell in the tableView. This was written with Swift 4.2.

//
//  ImageLabelCell.swift
//    -- provides an example of changing "tintColor" of UILabel to behave like other elements.
//    -- made problem a bit more complex by having a "selected" cell be in inverse colors - so also deal with background.
//

import UIKit

enum CellTypes: Int, CaseIterable { case cell1, cell2, cell3, cell4

    // This type provides a demonstration of a way to associate different titles and images to populate the UILabel and UIImageView in the cell.
    var title: String {
        return "\(self)".uppercased()
    }

    var imageName: String {
        return "\(self)"
    }
}

class ImageLabelCell: UITableViewCell {

    @IBOutlet weak var lblString: UILabel?
    @IBOutlet weak var imgView: UIImageView!

    fileprivate var type = CellTypes.cell1
    fileprivate var cellSelected = false

}

extension ImageLabelCell {

    func getFgBgColors() -> (UIColor, UIColor) {    // get foreground, background colors
        let white = UIColor.white
        var useTint = UIColor.blue  // Use your app color here.  Just ensures useTint is not nil.
        if let tint = tintColor {
            useTint = tint
        }
        if cellSelected {
            return (white, useTint)     // Selected cell is white on colored background
        } else {
            return (useTint, white)     // Unselected cell is colored on white background
        }
    }

    func configureCell(type: CellTypes, andSelected selected: Bool) {
        // Save properties we may use again later
        self.type = type
        self.cellSelected = selected

        // Set label text and image
        lblString?.text = type.title
        imgView.image = UIImage(named: type.imageName)

        // Set colors
        let (fgColor, bgColor) = getFgBgColors()
        imgView.tintColor = fgColor
        self.contentView.backgroundColor = bgColor
        lblString?.textColor = fgColor
    }

    override func tintColorDidChange() {
        // This gets called when the program tint color changes to gray (or back again) such as when popups appear/disappear.
        let (fgColor, bgColor) = getFgBgColors()
        // NOTE: need to set text color and background color.  Imageview can take care of itself.
        lblString?.textColor = fgColor
        self.contentView.backgroundColor = bgColor
    }

}

Simple solution may work for some usecases - use let fakeButton = UIButton(type: .system) which automatically adjust to window.tintColor.

Important thing is to set .system type which automatically match the window.tintColor.

You may also set fakeButton.isEnabled = false to prevent user interacting with the button. However, we didn't set target-action so the button is already the fake one.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top