What is the Dynamic Provider equivalent for UIImages? - ios

We can use dynamic providers to extent UIColor classes and have colors that change immediately to dark and light modes, like this
extension UIColor {
static var myControlBackground: UIColor {
return UIColor { (traits) -> UIColor in
// Return one of two colors depending on light or dark mode
return traits.userInterfaceStyle == .dark ?
UIColor(red: 0.5, green: 0.4, blue: 0.3, alpha: 1) :
UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1)
}
}
}
But what about UIImages?
I know that you can use this method
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if traitCollection.hasDifferentColorAppearance(comparedTo:
previousTraitCollection) {
// Color change detected.
// Adjust the interface accordingly.
}
}
the problem is that this method doe not detect changes if the app is in background when the change occurs. And the color dynamic method does.
Any ideas

You are looking for the UIImageAsset class:
https://developer.apple.com/documentation/uikit/uiimageasset
Basically, you make an image asset along with versions of the UIImage for different trait collections, and call register on the image asset with the image along with its corresponding trait collection.

Related

SFSafariViewController no dark mode with custom bar tint color

I call SFSafariViewController from my app to open myurl. I changed the bar tint color of SFSafariViewController. This works fine using:
vc.preferredBarTintColor = UIColor.teal025
However when device changes appearance style mode from light to dark, the bar tint color remains teal025 and doesn't adjust to a darker color, as default bar tint color does (e.g. light gray to dark gray) if not set using preferredBarTintColor.
Major parts of the app also use teal025 as navigation bar tint color. These object adjust color automatically as desired, when entering dark mode.
How can I have the same behavior for SFSafariViewController bar tint color?
Here the entire code:
let urlString = "https://myurl"
if let url = URL(string: urlString) {
let vc = SFSafariViewController(url: url)
vc.preferredBarTintColor = UIColor.teal025 // this prevents dark mode change
vc.preferredControlTintColor = UIColor.teal100
present(vc, animated: true)
}
NB1: I don't want to use UIWebView since myurl uses Google fonts, which don't show properly on iOS using UIWebView.
NB2: teal025 and teal100 are custom colors.
--- UPDATE --- (10.01.2021)
As requested, here how I define(d) my colors.
extension UIColor {
static var teal025: UIColor { return UIColor(red: 0.0, green: 127.0/255.0, blue: 127.0/255.0, alpha: 1.0) } // 0x008080
}
--- UPDATE --- (12.01.2021)
My aim is to have a SFSafariViewController bar tint which has exactly the same color tints, gradient and translucency than the app's navigation bar. My navigation is type Prefer Large Titles style along with scrollEdgeAppearance. These two properties handle automatic light/dark preference changes as well as translucency and vertical color gradients. I believe SFSafariViewController doesn't have provisions for all that. So the closest would be the dynamicProvider initializer of UIColor, as suggested by CSmith. This would "only" address the light/dark preference change.
let urlString = "https://myurl"
if let url = URL(string: urlString) {
let vc = SFSafariViewController(url: url)
vc.preferredBarTintColor = UIColor.safariBarTint
vc.preferredControlTintColor = UIColor.safariControlTint
present(vc, animated: true)
}
extension UIColor {
struct Material {
static var orangeA100: UIColor { return UIColor(red: 0xff / 0xff,
green: 0xd1 / 0xff,
blue: 0x80 / 0xff,
alpha: 1.0) } // #FFD180
...
static var orangeA700: UIColor { return UIColor(red: 0xff / 0xff,
green: 0x6d / 0xff,
blue: 0x00 / 0xff,
alpha: 1.0) } // #FF6D00
}
}
static var safariBarTint: UIColor {
if #available(iOS 13.0, *) {
return UIColor { (traits: UITraitCollection) -> UIColor in
return traits.userInterfaceStyle == .dark ?
UIColor.Material.orangeA700 :
UIColor.Material.orangeA100
}
} else { // for iOS 12 and earlier
return UIColor.Material.orangeA100
}
}
static var safariControlTint: UIColor {
if #available(iOS 13.0, *) {
return UIColor { (traits: UITraitCollection) -> UIColor in
return traits.userInterfaceStyle == .dark ?
UIColor.Material.orangeA100 :
UIColor.Material.orangeA700
}
} else { // for iOS 12 and earlier
return UIColor.Material.orangeA700
}
}
I believe a 1:1 color adaptation between app's navigation bar and SFSafariViewController must be done manually, hence remains a shot in the dark?!
I believe above code is the closest I can get.
I observe SFSafariViewController properly respecting the light and dark variants of the UIColor set as preferredBarTintColor. Your definition of teal025 in the UIColor extension defines a single color variant (0x008080) and does not have a separate dark mode color.
To resolve define your teal025 in a Color asset catalog and load the value using:
preferredBarTintColor = UIColor.init(named:"teal025")
Make sure to define both dark and light variants in the color asset by selecting "Any,Dark" for the setting Appearance, then set appropriate colors for each.
Or, use the dynamicProvider initializer of UIColor to return dark and light mode variants at run time, something like this:
extension UIColor
{
static var teal025: UIColor
{
if #available(iOS 13.0, *)
{
return UIColor { (traits: UITraitCollection) -> UIColor in
// Return one of two colors depending on light or dark mode
return traits.userInterfaceStyle == .dark ?
UIColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1) :
UIColor(red: 0.0, green: 0.4, blue: 0.4, alpha: 1)
}
}
else
{
// for iOS 12 and earlier
return UIColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1)
}
}
}
Lastly, and after better understanding your question, you're possibly perceiving that SFSafariViewController bar tint color looks different from your app because of bar translucency. At present there is no means of accessing the UINavigationBar of SFSafariViewController to disable translucency.

Colors that will be used throughout an app or a class

I'm trying to improve my object oriented skills and I'm always debating if a class is needed or not.
I have a set of UIColors that I'm constantly using throughout my app in different ViewControllers, I originally started by adding them as constant globals as follow...
import UIKit
// Global COLORS
let myBlueColor = UIColor(red: 62.0/255, green: 174.0/255, blue: 206.0/255, alpha: 1.0)
// more global colors here...
class ViewController1{
// using my global color
myButton.layer.borderColor = myBlueColor.CGColor
}
// other viewController
import UIKit
class ViewController2{
// using my global color again
myButton2.layer.borderColor = myBlueColor.CGColor
}
But then I decided to created a class to force myself to think in more oriented way like so...
Color Class
import Foundation
import UIKit
class Color {
var myBlueColor:UIColor{
get{
return UIColor(red: 62.0/255, green: 174.0/255, blue: 206.0/255, alpha: 1.0)
}
}
var myLightGrayColor:UIColor{
get{
return UIColor(red: 249.0/255, green: 249.0/255, blue: 249.0/255, alpha: 1.0)
}
}
var myGreenColor:UIColor{
get{
return UIColor(red: 110.0/255, green: 186.0/255, blue: 64.0/255, alpha: 1.0)
}
}
var myRedColor:UIColor{
get{
return UIColor(red: 247.0/255, green: 118.0/255, blue: 113.0/255, alpha: 1.0)
}
}
var myYellowColor:UIColor{
get{
return UIColor(red: 255.0/255, green: 190.0/255, blue: 106.0/255, alpha: 1.0)
}
}
}
View Controller
import UIKit
class ViewController1{
private var myColor = Color()
// some other code here...
myButton.layer.borderColor = myBlueColor.CGColor
}
Other View Controller
import UIKit
class ViewController1{
private var myColor = Color()
// some other code here...
myButton2.layer.borderColor = myBlueColor.CGColor
}
Is my object oriented a better approach? Ok let me rephrase this, is this even how you would do it in a more object oriented way?
I don't know but my Color class looks weird by just using getters (computed properties).
Any suggestions to improve my code.
EDIT: Not a duplicate because I was interested on improving my object oriented example more than knowing if globals were ok to use.
Thanks
Definitely a good idea, but you can probably make it even more clear by just extending UIColor and making those all class functions, exactly the same way as the built-in UIColor.whiteColor(), etc.
You can do that like this:
extension UIColor {
class func peachColor() -> UIColor {
return UIColor(colorLiteralRed: 100.00 / 100.00, green: 92.9 / 100.0, blue: 65.9 / 100.0, alpha: 1.0)
}
...
}
Then, anywhere in your app, you can say UIColor.peachColor() and it'll work perfectly. These class extensions are a clean way to do it, and it'll allow you to avoid putting a var myColor = Color() in each view controller.
If you want to encapsulate your Colors in a class, and they will not change, the best way to do so in an OOP environment is by using public static final variables.
(My Swift is very rusty at best so this may be incorrect syntax but it should be clear nonetheless.)
final struct MyColors {
public static final blue:UIColor = UIColor(red: 62.0/255, green: 174.0/255, blue: 206.0/255, alpha: 1.0)
// etc
}
You can then reference the value by using MyColors.blue wherever you want, and that will refer to the single defined UIColor object.
Assuming that you're trying to theme your app globally with colors, you ought to look into Apple's Appearance API, UIAppearance.
For example, to set the default color of all UINavigationBars in your app, you would call:
UINavigationBar.appearance().tintColor = myColor
You can also set other appearance properties such as background images
For more info, see:
http://nshipster.com/uiappearance/
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAppearance_Protocol/
https://gist.github.com/mattt/5135521

Add a custom palette to IB that propagates if a color changes iOS

I want to create a custom palette in IB where you can set colors, and if a color changes in that palette it propagates through the views. I've seen this approach http://natashatherobot.com/xcode-color-palette/, but if in the future, a color of the palette changes, you have to go to every view on the project and change that color. I tried also doing an #IBInspectable but you can't have enums (so you can map an enum to a color). I know i can just define the colors by code and then having an outlet, but the problem is I a have tons of views that I need to subclass just for changing a color like this:
class TestCommonView: CommonView {
#IBOutlet weak var borderView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
borderView.backgroundColor = Colors.fabrica.pastel
}
}
Any ideas?
Reconsidering this statement:
I tried also doing an #IBInspectable but you can't have enums (so you can map an enum to a color).
You can bridge over an #IBInspectable enum using Int this way:
enum ColorPalette:Int {
case clear = 0
case teal = 1
case plum = 2
case foam = 3
}
var tincture:ColorPalette = .clear
In code, access self.tincture, which is the enum you are after.
In Interface Builder, use tinctureAdapter which is an Int, and therefore a de-facto #IBInspectable of type enum.
// Stored IB property
#available(*, unavailable, message="Use tincture programmatically")
#IBInspectable var tinctureAdapter:Int {
get {
return self.tincture.rawValue
}
set {
self.tincture = ColorPalette:Int(rawValue: newValue) ?? .clear
}
}
It may be useful to place this code in a UIColor class Extension. Using the bridge approach, you could also use #Donamite plain-English strings in IB.
Swift 3 Example
#IBDesignable class ColorSwatchView: UIView {
enum ColorPalette: String {
case Thistle = "thistle"
case Plum = "plum"
case Olive = "olive"
case Iris = "iris"
case Clear = "clear"
}
let namedColors = [
"thistle": UIColor(red: 216/255, green: 191/255, blue: 216/255, alpha: 1),
"plum" : UIColor(red: 221/255, green: 160/255, blue: 221/255, alpha: 1),
"olive" : UIColor(red: 128/255, green: 128/255, blue: 0/255, alpha: 1),
"iris" : UIColor(red: 3/255, green: 180/255, blue: 200/255, alpha: 1)
]
var tincture:ColorPalette = .Clear
// Stored IB property
#available(*, unavailable, message: "Use tincture programmatically")
#IBInspectable var tinctureName: String? {
willSet {
if let color = ColorPalette(rawValue: newValue?.lowercased() ?? "") {
tincture = color
}
}
}
}
Interface Builder
Make your custom view a child of ColorSwatchView.
Programmatically
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.saveGState()
let uiColor = namedColors[tincture.rawValue] ?? UIColor.clear
ctx?.setFillColor(uiColor.cgColor)
ctx?.fill(bounds)
ctx?.restoreGState()
}
► Find this solution on GitHub and additional details on Swift Recipes.

What's the default color for placeholder text in UITextField?

Does anyone know what color a UITextField's placeholder text is, by default? I'm trying to set a UITextView's text to the same color. I've read elsewhere that it is UIColor.lightGrayColor() but it is actually a little lighter.
The colour is #C7C7CD (r: 199 g:199 b: 205) (as what pterry26 said)
and the font-family is HelveticaNeue-Medium and size is 16
Note that this is a guess at what the color looks like on a screen. For the actual values, simply inspect the Apple code for attributedPlaceholder.
You can get this colour from inspecting the attributedPlaceholder from the UITextField.
The default seems to be: NSColor = "UIExtendedSRGBColorSpace 0 0 0.0980392 0.22";
You could add an extension (or category) on UIColor:
extension UIColor {
static var placeholderGray: UIColor {
return UIColor(colorLiteralRed: 0, green: 0, blue: 0.0980392, alpha: 0.22)
}
}
2018, latest syntax is just:
extension UIColor {
static var officialApplePlaceholderGray: UIColor {
return UIColor(red: 0, green: 0, blue: 0.0980392, alpha: 0.22)
}
}
#colorLiteralRed was deprecated. Be aware of this in some cases.
Just to add that in iOS 13 (and later), the placeholder color is exposed by Apple via
UIColor.placeholderText
and it's dynamic (supports both dark and light).
Putting it with pre-iOS 13:
static var placeholderText: UIColor {
if #available(iOS 13.0, *) {
return .placeholderText
}
return UIColor(red: 60, green: 60, blue: 67)!.withAlphaComponent(0.3)
}
I sent a screenshot to my mac and used Photoshop's eyedropper tool. For anyone interested, this is at least a very good approximation of the placeholder color on a white background:
Red: 199,
Green: 199,
Blue: 205
The actual color is not a solid one but has transparency in it. So the closest color is
Red: 4, Green: 4, Blue: 30, Alpha: ~22%
If you use this with a white background you will get what #pterry26 wrote above.
Using the values from the correct answer above
extension UIColor {
class func greyPlaceholderColor() -> UIColor {
return UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0)
}
}
According to the Apple code, it is 70% gray
open var placeholder: String? // default is nil. string is drawn 70% gray
and if we convert it to rgb :
UIColor.init(red: 178/255, green: 178/255, blue: 178/255, alpha: 1)
Starting from iOS 13 you should use UIColor.placeholderText to make sure the element looks good in both light and dark modes. Documentation:
The color for placeholder text in controls or text views.
I believe it is R:191 G:191 B:198 A:1. See image below. Here marked controls are UIButton with above Title TextColor and rest are UITextFields with default placeholder color. If iOS makes difference, then this one is iOS 9.
The code #999999 matches perfectly on my form!
Better to grab the color off of a text field dynamically in case it changes in the future. Default to 70% gray, which is pretty close
extension UITextField {
var placeholderColor: UIColor {
return attributedPlaceholder?.attributes(at: 0, effectiveRange: nil)[.foregroundColor] as? UIColor ?? UIColor(white: 0.7, alpha: 1)
}
}
I think you can get the color by use the tools in your Mac.
Set label font to "light"
mylabel.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:14.0f];
and color code for placeholder text is #c2b098

Where do you put a global method in swift?

I have a function that is common to all my controllers:
func myColor() -> UIColor {
return UIColor(red: 9.0/255.0, green: 134.0/255.0, blue: 255.0/255.0, alpha: 1)
}
Where can I put this function, so I can access it from any controller?
By default all default access scope (internal) functions are available everywhere in the application. If you have this function defined in different module, you need to use public modifier.
To make your code clearer it is best to create extension for UIColor.
extension UIColor {
class func myColor() -> UIColor {
return UIColor(red: 9.0/255.0, green: 134.0/255.0, blue: 255.0/255.0, alpha: 1)
}
}
Then you can use myColor same way as default UIColor colors.
let systemColor = UIColor.blackColor()
let myColor = UIColor.myColor()
1) Is your function returning the same color every time as in the question? In that case why don't you make it a static color in AppDelegate which you can access anywhere using
(UIApplication.sharedApplication() as AppDelegate).myColor
2) If your color will return a different color everytime based on your class properties but uses the same formula for all classes for e.g.
func myColor() -> UIColor {
return UIColor(red: (prop1 * 5)/255.0, green: prop2/255.0, blue: (prop3/2)/255.0, alpha: 1)
}
you can define prop1,prop2,prop3 and the function in a base class which every class can override and set their own values for the properties. The function will not need to be overridden.
3) If the formula for calculating the color is different for every class, you can try making a protocol which defines this function and the properties. Every class which inherits from this property will have to provide their own implementation of the formula.
You can pick the appropriate solution based on your need.
I hope this helped!
Add a swift file name it UIColorExt.swift:
import UIKit
class func RGB(r: CGFloat, _ g: CGFloat, _ b: CGFloat, _ alpha: CGFloat = 1.0) -> UIColor{
return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha)
}
Usage:
view.backgroundColor = UIColor.RGB(204, 119, 70)

Resources