Dark mode for custom SwiftUI Color - swiftui

I get a series of colors with different hues from my designer. Here is how I make them
public struct Colors {
public static let blue = Blue()
public static let grey = Grey()
public static let black = Black()
public static let green = Green()
public static let orange = Orange()
public static let red = Color(hexString: "#F8454D")
public static let yellow = Color(hexString: "#FFAE03")
public init() {
}
}
public struct Blue {
public let light: Color = Color(hexString: "9AB1D0")
public let medium: Color = Color(hexString: "215499")
public let dark: Color = Color(hexString: "153662")
}
public struct Grey {
public let light: Color = Color(hexString: "CCCDD0")
public let medium: Color = Color(hexString: "757780")
public let dark: Color = Color(hexString: "404146")
}
public struct Black {
public let light: Color = Color(hexString: "A2A4A6")
public let medium: Color = Color(hexString: "33383D")
public let dark: Color = Color(hexString: "0A0B0C")
}
public struct Green {
public let light: Color = Color(hexString: "ACD3BA")
public let medium: Color = Color(hexString: "499F68")
public let dark: Color = Color(hexString: "285739")
}
public struct Orange {
public let light: Color = Color(hexString: "F4BBA5")
public let medium: Color = Color(hexString: "E76B39")
public let dark: Color = Color(hexString: "542715")
}
None of these 'Color''s respond to dark mode automatically like system provided Colors's do.
How do I assign an "inverse" color so that I can take advantage of dark mode without using system colors?

One way to assign an "inverse" color of your liking so that you can take advantage of dark mode without using system colors, is this:
public struct Colors {
public var blue: MyColor = Blue()
.....
public init(colorScheme: ColorScheme) {
self.blue = colorScheme == .light ? Blue() : BlueForDarkMode()
.....
}
}
public protocol MyColor {
var light: Color { get }
var medium: Color { get }
var dark: Color { get }
}
// Orange for testing
public struct BlueForDarkMode: MyColor {
public let light: Color = Color(hexString: "F4BBA5")
public let medium: Color = Color(hexString: "E76B39")
public let dark: Color = Color(hexString: "542715")
}
public struct Blue: MyColor {
public let light: Color = Color(hexString: "9AB1D0")
public let medium: Color = Color(hexString: "215499")
public let dark: Color = Color(hexString: "153662")
}
then you call it like this:
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
#State var myColors = Colors(colorScheme: .light)
var body: some View {
Rectangle()
.frame(width: 300, height: 300)
.foregroundColor(myColors.blue.medium)
.onAppear(perform: {self.myColors = Colors(colorScheme: self.colorScheme)})
}
}
Note, you will have to do a bit more work to "sense" mode changes, but that's for another question.

Related

Update #StateObject every time that init() is invoked in SwiftUI

I'm writing a SwiftUI code for a carousel, and I have a CarouselModel class that goes like this:
CarouselModel.swift
import Foundation
import SwiftUI
class CarouselModel: ObservableObject {
//MARK: - Properties
#Published private var imagesCount: Int
#Published private var currentImage: Int
#Published private var offsetMainAxis: CGFloat
private var screenSize: CGFloat
private var isHorizontal: Bool
private var padding: CGFloat
private var nextImage: Int {
return (self.currentImage+1) % self.imagesCount
}
private var prevImage: Int {
return (self.currentImage-1+self.imagesCount) % self.imagesCount
}
private var centralIndex: CGFloat {
return CGFloat(self.imagesCount-1)/2
}
public var computedOffset: CGFloat {
return (centralIndex-CGFloat(currentImage))*(screenSize-padding) + offsetMainAxis
}
init(imagesCount: Int, isHorizontal: Bool = true, padding: CGFloat = 20, currentImage: Int = 0) {
self.imagesCount = imagesCount
self.currentImage = currentImage
self.offsetMainAxis = 0
self.padding = padding
self.isHorizontal = isHorizontal
if isHorizontal {
self.screenSize = UIScreen.main.bounds.width
} else {
self.screenSize = UIScreen.main.bounds.height
}
}
//MARK: - Methods
public func getCurrentImage() -> Int {
return self.currentImage
}
public func goNext() -> Void {
withAnimation(.easeOut(duration: 0.5)) {
currentImage = nextImage
}
}
public func goPrevious() -> Void {
withAnimation(.easeOut(duration: 0.5)) {
currentImage = prevImage
}
}
public func onDragChange(offset: CGFloat) -> Void {
self.offsetMainAxis = offset
}
public func onDragEnd() -> Void {
if abs(offsetMainAxis) > 0.2*screenSize {
if offsetMainAxis > 0 {
withAnimation(.easeOut(duration: 0.5)) {
offsetMainAxis = 0
currentImage = prevImage
}
} else {
withAnimation(.easeOut(duration: 0.5)) {
offsetMainAxis = 0
currentImage = nextImage
}
}
} else {
withAnimation(.easeOut(duration: 0.5)) {
offsetMainAxis = 0
}
}
}
public func skipTo(number i: Int) {
withAnimation(.easeOut(duration: 0.5)) {
currentImage = i
}
}
}
At this point every time I need to place a concrete Carousel in my app I create a CarouselView.swift file containing some View, and the thing goes more or less like this:
CarouselView.swift
import SwiftUI
struct Carousel: View {
#StateObject var carousel: CarouselModel
private var screenWidth: CGFloat = UIScreen.main.bounds.width
private var images: [String]
private let padding: CGFloat
private var descriptions: [String]?
#State private var imageSelected: [String:Bool]
init(images: [String], descriptions: [String]?, padding: CGFloat = 20) {
self.images = images
self.padding = padding
_carousel = StateObject(
wrappedValue:
CarouselModel(
imagesCount: images.count,
isHorizontal: true,
padding: padding
)
)
self.imageSelected = Dictionary(uniqueKeysWithValues: images.map{ ($0, false) })
guard let desc = descriptions else {
self.descriptions = nil
return
}
self.descriptions = desc
}
var body: some View {
// USE carousel TO DISPLAY SOME CAROUSEL IMPLEMENTATION
}
}
Now, a problem arises when inside some container I need to display a different set of images based on a #State private var selectedImgSet: Int and each set has a different amount of pictures in it. The point is, whenever I call CarouselView(...) inside the container and its parameters change, its init is invoked but the CarouselModel doesn't get updated with the new images count.
This is totally expected since it is a #StateObject but I'm not sure how the update should happen. I can't use the .onAppear(perform: ) hook in the CarouselView since that is executed only once, when the CarouselView first appears.
What is the correct way to update the carousel: CarouselModel variable whenever CarouselView's init parameters change?
EDIT: Here are pastes for a full working examples: ContentView, CarouselItem, CarouselModel, CarouselView, Items. Add the following images sets to your assets: item00, item01, item02, item10, item11, item20, item21, item22, item23. Any 1920x1080p placeholder will do the trick for now.
I eventually fixed my issue the following way: I introduced the following method in CarouselModel:
public func update(_ images: Int) {
print("Performing update with \(images) images")
self.imagesCount = images
self.currentImage = 0
self.offsetMainAxis = 0
}
Then I added to the outmost View inside CarouselView the following modifier:
.onChange(of: self.images) { v in
self.carousel.update(v.count)
}
Now the update happens as expected.
You've got things the wrong way round. Create the #StateObject in the View above Carousel View and pass it in as #ObservedObject. However, since your model does no loading or saving it doesn't need to be a reference type, i.e. an object. A value type will suffice, i.e. a struct, so it can be #State in the parent and pass in as #Binding so you can call its mutating functions.

SwiftUI - Resize AVCaptureVideoPreviewLayer

I have the following code:
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera: Camera
class CameraView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
func makeUIView(context: Context) -> CameraView {
let cameraView = CameraView()
cameraView.videoPreviewLayer.session = camera.session
cameraView.videoPreviewLayer.videoGravity = .resizeAspect
cameraView.videoPreviewLayer.cornerRadius = 12
cameraView.videoPreviewLayer.connection?.videoOrientation = .portrait
cameraView.videoPreviewLayer.backgroundColor = CGColor(red: 1.0, green: 0, blue: 0, alpha: 1)
camera.start()
return cameraView
}
func updateUIView(_ cameraView: CameraView, context: Context) {
}
}
But I haven’t been able to resize it in my SwiftUI View to the camera’s preview true size, I’m stuck where I have a bigger frame than it actually needs:
Is there a way to make the AVCaptureVideoPreviewLayer/UIViewRepresentable frame only the size of the camera preview?
set cameraView.videoPreviewLayer.videoGravity = .resize
it fits.

Button won't show disabled styling

I made a struct that styles my buttons by passing a type and size parameters. I would like the button to change style when it's disabled, but it's not working. The button does toggle between disabled and enabled, but the style is always the enabled style.
struct CustomButton: ButtonStyle {
enum ButtonStyle {
case button, destructive, light
}
enum ButtonSize {
case normal, large, small
}
#Environment(\.isEnabled) var isEnabled
private var maxWidth : CGFloat
private var padding : CGFloat
private var foreground : Color
private var foregroundDisabled : Color
private var strokeColor : Color
private var strokeDisabled : Color
private var strokeWidth : CGFloat
private var background : Color
private var backgroundDisabled : Color
private var fontSize : Font
init(style: ButtonStyle, size: ButtonSize) {
switch size {
case .large:
self.maxWidth = .infinity
self.padding = 15.0
self.fontSize = Font.system(.title3, design: .rounded).weight(.bold)
case .small:
self.maxWidth = 200
self.padding = 8.0
self.fontSize = Font.system(.callout, design: .rounded)
default:
self.maxWidth = .infinity
self.padding = 12.0
self.fontSize = Font.system(.body, design: .rounded)
}
switch style {
case .light:
strokeColor = .main
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall: strokeLarge
foreground = .main
foregroundDisabled = .gray
background = .clear
backgroundDisabled = .clear
case .destructive:
strokeColor = .destructive
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall : strokeLarge
foreground = .destructive
foregroundDisabled = .destructive
background = .clear
backgroundDisabled = .clear
default:
strokeColor = .clear
strokeDisabled = .clear
strokeWidth = 0.0
foreground = .white
foregroundDisabled = .white
backgroundDisabled = .gray
background = .main
}
}
func makeBody(configuration: Self.Configuration) -> some View {
return configuration.label
.frame(maxWidth: maxWidth)
.font(fontSize)
.foregroundColor(isEnabled ? foreground : Color.red)
.padding(padding)
.background(RoundedRectangle(cornerRadius: roundedCorner)
.strokeBorder(isEnabled ? strokeColor : strokeDisabled, lineWidth: strokeWidth)
.background(isEnabled ? background : backgroundDisabled)
)
.clipShape(RoundedRectangle(cornerRadius: roundedCorner))
.opacity(configuration.isPressed ? 0.8 : 1.0)
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
}
}
And it's added to a view like this:
Button(action: {
//Some logic here
}, label: {
Text(NSLocalizedString("Add Group", comment: "button"))
})
.disabled(selections.count < 2) //the button does become disabled (but doesn't change style)
.buttonStyle(CustomButton(style: .button, size: .normal))
The #Environment is not injected in style, it is only for views, so here a demo of possible solution - based on internal helping wrapper view.
struct CustomButton: ButtonStyle {
enum ButtonStyle {
case button, destructive, light
}
enum ButtonSize {
case normal, large, small
}
private var maxWidth : CGFloat
private var padding : CGFloat
private var foreground : Color
private var foregroundDisabled : Color
private var strokeColor : Color
private var strokeDisabled : Color
private var strokeWidth : CGFloat
private var background : Color
private var backgroundDisabled : Color
private var fontSize : Font
init(style: ButtonStyle, size: ButtonSize) {
switch size {
case .large:
self.maxWidth = .infinity
self.padding = 15.0
self.fontSize = Font.system(.title3, design: .rounded).weight(.bold)
case .small:
self.maxWidth = 200
self.padding = 8.0
self.fontSize = Font.system(.callout, design: .rounded)
default:
self.maxWidth = .infinity
self.padding = 12.0
self.fontSize = Font.system(.body, design: .rounded)
}
switch style {
case .light:
strokeColor = .main
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall: strokeLarge
foreground = .main
foregroundDisabled = .gray
background = .clear
backgroundDisabled = .clear
case .destructive:
strokeColor = .destructive
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall : strokeLarge
foreground = .destructive
foregroundDisabled = .destructive
background = .clear
backgroundDisabled = .clear
default:
strokeColor = .clear
strokeDisabled = .clear
strokeWidth = 0.0
foreground = .white
foregroundDisabled = .white
backgroundDisabled = .gray
background = .main
}
}
private struct EnvReaderView<Content: View>: View { // << this one !!
let content: (Bool) -> Content
#Environment(\.isEnabled) var isEnabled // read environment
var body: some View {
content(isEnabled) // transfer into builder
}
}
func makeBody(configuration: Self.Configuration) -> some View {
EnvReaderView { isEnabled in // now we can use it !!
configuration.label
.frame(maxWidth: maxWidth)
.font(fontSize)
.foregroundColor(isEnabled ? foreground : Color.red)
.padding(padding)
.background(RoundedRectangle(cornerRadius: roundedCorner)
.strokeBorder(isEnabled ? strokeColor : strokeDisabled, lineWidth: strokeWidth)
.background(isEnabled ? background : backgroundDisabled)
)
.clipShape(RoundedRectangle(cornerRadius: roundedCorner))
.opacity(configuration.isPressed ? 0.8 : 1.0)
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
}
}
}

Change background color when dark mode turns on in SwiftUI

I've created a custom sheet in SwiftUI with the background color White .background(Color.white)
Now I want the background color to change to black when the user turns on the dark mode on iOS.
But I can't find a dynamic color for background like Color.primary for colors of the text etc.
So is there any way to change the background color to black when dark mode turns on?
To elaborate on the two existing answers, there are a couple of approaches to making the background change based on light or dark mode (aka colorScheme) depending on what you're trying to achieve.
If you set the background color to white because that's the default background color, and you want the system to be able to update it when the user switches to dark mode, change .background(Color.white) to .background(Color(UIColor.systemBackground)) (umayanga's answer).
e.g.
// Use default background color based on light/dark mode
struct ContentView: View {
...
var body: some View {
// ... to any view
.background(Color(UIColor.systemBackground))
}
If you want to customize the color of a view based on the device being in light or dark mode, you can do this (from Asperi's answer):
// Use custom background color based on light/dark mode
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
...
var body: some View {
// ... to any view
.background(colorScheme == .dark ? Color.black : Color.white)
}
Note that many SwiftUI views set their background color to .systemBackground by default, so if you're using a ScrollView, List, Form, etc, they'll use the default system background color and you won't need to use .background unless you want to customize it.
If you want something that works directly from Color (like you're doing with Color.primary), and functions on both iOS and macOS (UIColor won't work on macOS), you can use the following simple Color extension, which uses conditional compilation to work correctly on either OS.
You then simply access these from elsewhere in your code like any other SwiftUI Color. For example:
let backgroundColor = Color.background
No need to check colorScheme or userInterfaceStyle with this approach: The OS will switch automatically when the user moves between Light & Dark mode.
I've also included 'secondary' & 'tertiary' colors, which are a little subjective on macOS, but you can always change them to some of the other NSColor properties if you want.
Swift v5.2:
import SwiftUI
public extension Color {
#if os(macOS)
static let background = Color(NSColor.windowBackgroundColor)
static let secondaryBackground = Color(NSColor.underPageBackgroundColor)
static let tertiaryBackground = Color(NSColor.controlBackgroundColor)
#else
static let background = Color(UIColor.systemBackground)
static let secondaryBackground = Color(UIColor.secondarySystemBackground)
static let tertiaryBackground = Color(UIColor.tertiarySystemBackground)
#endif
}
Change the .background(Color.white) to .background(Color(UIColor.systemBackground))
We can also change the color automatically by adding them to the Assets folder.
Add a new color set in the Assets folder
After you add a color set, you can name it as per your convenience and you can configure your color for Any Appearance, Dark Appearance, Light Appearance.
To access your newly added color set, you need to follow the following initializer syntax of Color
Color("your_color_set_name")
For best practice you would not want your code filled with string values of your Color set name. You can create an extension to make usage more pragmatic and ordered.
extension Color {
static var tableViewBackground: Color {
Color("tableViewBackground")
}
}
Here is possible approach (for any color)
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
...
var body: some View {
// ... to any view
.background(colorScheme == .dark ? Color.black : Color.white)
}
}
If you wish to use custom background colour for light/dark mode then I would recommend creating New Colour set in your Assets folder with custom colour values for different Appearances.
That way background colour will change automatically when display mode is switched without the need of adding a single line of code.
And then using this color of the colour list for Controller View background.
Personally I don't like to create a color set in the Assets folder.
I prefer it to be in the code so the best practices for this are as follows:
extension Color {
static var primaryColor: Color {
Color(UIColor { $0.userInterfaceStyle == .dark ? UIColor(red: 255, green: 255, blue: 255, alpha: 1) : UIColor(red: 200, green: 200, blue: 200, alpha: 1) })
}
}
Using:
.background(Color.primaryColor)
Edit #2
Here are 2 completes solutions:
Giving a control of mode status (Light or Dark) at Application Level
Giving a control of mode status at View Level
Both are updated on the fly when Mode status changes.
Both work in Preview and on true device.
Both manage a Color Theme based on Mode status (inspired from JetPack Compose).
In solution 1, OSModeTheme & OSModeThemeUpdater work together to provide the right mode status and color theme as static values, offering the possibility to define colors at the top level, eg: in default values of a view init parameters.
init(color: Color = OSModeTheme.colors.primary) { ... }
In solution 2, OSModeThemeProvider is a View Wrapper providing a local variable containing the right ColorTheme according to the Mode status.
OSModeThemeProvider { colors in
Text("Foo bar")
.foregroundColor(colors.primary)
}
// Commun part
protocol Palette {
static var primary: Color { get }
static var primaryVariant: Color { get }
static var secondary: Color { get }
static var secondaryVariant: Color { get }
static var accentColor: Color { get }
static var background: Color { get }
static var frame: Color { get }
static var error: Color { get }
}
struct LightColorPalette: Palette {
static var primary = ColorPalette.black
static var primaryVariant = ColorPalette.grayDark
static var secondary = ColorPalette.grayMid
static var secondaryVariant = ColorPalette.grayLight
static var accentColor = ColorPalette.blue
static var background = ColorPalette.white
static var frame = ColorPalette.grayDark
static var error = ColorPalette.orange
}
struct DarkColorPalette: Palette {
static var primary = ColorPalette.white
static var primaryVariant = ColorPalette.grayLight
static var secondary = ColorPalette.grayLight
static var secondaryVariant = ColorPalette.grayMid
static var accentColor = ColorPalette.blue
static var background = ColorPalette.black
static var frame = ColorPalette.grayLight
static var error = ColorPalette.orange
}
// Solution 1
class OSModeTheme {
static var colorScheme: ColorScheme = .light
static var colors: Palette.Type = LightColorPalette.self
static func update(mode: ColorScheme) {
colorScheme = mode
colors = colorScheme == .dark ? DarkColorPalette.self : LightColorPalette.self
}
}
struct OSModeThemeUpdater<Content>: View where Content: View {
#Environment(\.colorScheme) var colorScheme
let content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
var body: some View {
OSModeTheme.update(mode: colorScheme)
return content()
}
}
struct OSModeThemeDemo: View {
var body: some View {
OSModeThemeUpdater {
ZStack {
Rectangle()
.fill(OSModeTheme.colors.background)
VStack {
Group {
Text("primary")
.foregroundColor(OSModeTheme.colors.primary)
Text("primaryVariant")
.foregroundColor(OSModeTheme.colors.primaryVariant)
Text("Secondary")
.foregroundColor(OSModeTheme.colors.secondary)
Text("secondaryVariant")
.foregroundColor(OSModeTheme.colors.secondaryVariant)
Text("accentColor")
.foregroundColor(OSModeTheme.colors.accentColor)
Text("background")
.foregroundColor(OSModeTheme.colors.background)
Text("frame")
.foregroundColor(OSModeTheme.colors.frame)
Text("error")
.foregroundColor(OSModeTheme.colors.error)
}
}
}
}
}
}
// Solution 2
struct OSModeThemeProvider<Content>: View where Content: View {
#Environment(\.colorScheme) var colorScheme
let content: (Palette.Type) -> Content
init(#ViewBuilder content: #escaping (Palette.Type) -> Content) {
self.content = content
}
var body: some View {
content(colorScheme == .dark ? DarkColorPalette.self : LightColorPalette.self)
}
}
struct OSModeThemeProviderDemo: View {
var body: some View {
OSModeThemeProvider { palette in
ZStack {
Rectangle()
.fill(palette.background)
VStack {
Text("primary")
.foregroundColor(palette.primary)
Text("primaryVariant")
.foregroundColor(palette.primaryVariant)
Text("Secondary")
.foregroundColor(palette.secondary)
Text("secondaryVariant")
.foregroundColor(palette.secondaryVariant)
Text("accentColor")
.foregroundColor(palette.accentColor)
Text("background")
.foregroundColor(palette.background)
Text("frame")
.foregroundColor(palette.frame)
Text("error")
.foregroundColor(palette.error)
}
}
}
}
}
You can extend UIColor as shown below
extension UIColor{
struct Custom {
static var black: UIColor{
if #available(iOS 13, *) {
return UIColor.init { (trait) -> UIColor in
return trait.userInterfaceStyle == .dark ? UIColor.white : UIColor.black
}
}
return UIColor.black
}
}
}
Then use as .background(Color(UIColor.Custom.black))
Your view will update the color when dark move is enabled/disabled
Check out this page for recommended system colors for various UI elements. Using these should take care of dark/light mode switching.
UI Element Colors
Here is my solution. Inspired by Luc-Oliver's solution 2.
extension ColorScheme {
typealias ColorSelector = (Color, Color) -> Color
private var colorSelector: ColorSelector { color(_:_:) }
func color(_ light: Color, _ dark: Color) -> Color {
switch self {
case .light: return light
case .dark: return dark
#unknown default: fatalError()
}
}
struct Provider<Content>: View where Content: View
{
typealias ColorSelector = ColorScheme.ColorSelector
typealias ContentGetter = (ColorSelector) -> Content
#Environment(\.colorScheme) var colorScheme
let content: ContentGetter
init(#ViewBuilder content: #escaping ContentGetter) {
self.content = content
}
var body: some View {
content(colorScheme.colorSelector)
}
}
}
Use like:
struct DemoView: View {
var body: some View {
ColorScheme.Provider { color in
HStack {
Rectangle().fill(color(.blue, .red))
Rectangle().fill(color(.green, .orange))
}
}
}
}
I built a bridge between UIColor and Color:
fileprivate extension UIColor {
static func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
UIColor {
switch $0.userInterfaceStyle {
case .dark:
return dark
default:
return light
}
}
}
}
extension Color {
static var itemTextColor: Color = Color(uiColor: .dynamicColor(
light: UIColor(red:0.278, green:0.278, blue:0.278, alpha: 1.0),
dark: UIColor(red:0.800, green:0.800, blue:0.800, alpha:1.000))
)
}
If you want to switch between black and white color, make sure you don't set the
.foregroundColor(.black)
or
.foregroundColor(.white)
because it will force the color to be black on both dark and light mode.
If you remove it, the dark mode will have white color, and light mode will have black color.
SwiftUI has colors that adapt automatically to the color scheme. For example, you can use .background(Color(.textBackgroundColor)) to get an appropriate background color for rendering text regardless of color scheme.

Override UIButton backgroundColor property with protocol

I have a protocol like
protocol ButtonPresentable {
var backgroundColor: UIColor { get }
}
extension ButtonPresentable {
var backgroundColor: UIColor? {
return UIColor.red
}
}
I have a button custom class (draw with IB) that implements my protocol
class MyButton: UIButton, ButtonPresentable {
}
Why this protocol doesn't 'override' native backgroundColor property ?
I would like to have a default protocol implementation that will set a default background to all buttons. Is there a protocol oriented way to set default values to all UIButton background colors ?
EDIT: Actually I do that
protocol ButtonPresentable {
var pBackgroundColor: UIColor { get }
}
extension ButtonPresentable {
var pBackgroundColor: UIColor? {
return UIColor.red
}
func applyTheme() {
self.backgroundColor = pBackgroundColor
}
}
// In my custom view
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.applyTheme()
}
1. Why this protocol doesn't 'override' native backgroundColor property ?
Because: Extensions can add new functionality to a type, but they cannot override existing functionality.
Look at this example:
protocol ButtonProtocol {
var name: String { get }
}
extension ButtonProtocol {
var name: String {
return "button protocol"
}
}
class ButtonClass: ButtonProtocol {
var name: String = "button class"
}
let button = ButtonClass()
print(button.name) // prints: button class
2. Is there a protocol oriented way to set default values to all UIButton background colors ?
And if you want to override the backgroundColor of all buttons you could do this: once the button is set with its frame, you can override its background color:
extension UIButton {
open override var frame: CGRect {
didSet {
self.backgroundColor = .red
}
}
}