I'm doing a storyboardUI app. One part of the UI design is kind of like this:
I want the label position follows the slider position all the time, like below:
How can I do it?
Assign this class to UISlider.
In this class created one label and change the position according to the slider thumb.
class ThumbTextSlider: UISlider {
private var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
private lazy var thumbView: UIView = {
let thumb = UIView()
return thumb
}()
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = CGRect(x: thumbFrame.origin.x, y: thumbFrame.maxY - 5, width: thumbFrame.size.width, height: 30)
self.setValue()
}
private func setValue() {
thumbTextLabel.text = String(format: "%0.2f", self.value)
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.textAlignment = .center
thumbTextLabel.textColor = .blue
thumbTextLabel.layer.zPosition = layer.zPosition + 1
thumbTextLabel.adjustsFontSizeToFitWidth = true
}
}
Edit: Result of the code right now: https://imgur.com/a/tICLCdg - what I want: https://uxplanet.org/neumorphism-in-user-interface-tutorial-c353698ac5c0
I am trying to create a custom UIView class to get an Neumorphism lookalike View:
import UIKit
class NeumorphicView: UIView {
override public class var layerClass: Swift.AnyClass {
return CAShapeLayer.self
}
override func awakeFromNib() {
super.awakeFromNib()
// Light shadow
guard let shadowLayer = self.layer as? CAShapeLayer else {return}
shadowLayer.backgroundColor = UIColor(red:0.88, green:0.90, blue:0.93, alpha:1.0).cgColor
shadowLayer.shadowColor = UIColor(red:1.00, green:1.00, blue:1.00, alpha:1.0).cgColor
shadowLayer.cornerRadius = 15
shadowLayer.shadowOffset = CGSize(width: -3.0, height: -3.0)
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 3
}
}
With this code, I already get a brighter pseudo border on the left and top. Now I want to add an darker shadow on the right and bottom.
I tried to copy the code above with different values, but obviously I had no success. How can I add a shadow border to my existing code?
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I to create my own custom progressbar ie:NSProgressIndicator using swift how do I do that?
You can just make your own progress indicator, e.g.:
#IBDesignable
open class ColorfulProgressIndicator: NSView {
#IBInspectable open var doubleValue: Double = 50 { didSet { needsLayout = true } }
#IBInspectable open var minValue: Double = 0 { didSet { needsLayout = true } }
#IBInspectable open var maxValue: Double = 100 { didSet { needsLayout = true } }
#IBInspectable open var backgroundColor: NSColor = .lightGray { didSet { layer?.backgroundColor = backgroundColor.cgColor } }
#IBInspectable open var progressColor: NSColor = .blue { didSet { progressShapeLayer.fillColor = progressColor.cgColor } }
#IBInspectable open var borderColor: NSColor = .clear { didSet { layer?.borderColor = borderColor.cgColor } }
#IBInspectable open var borderWidth: CGFloat = 0 { didSet { layer?.borderWidth = borderWidth } }
#IBInspectable open var cornerRadius: CGFloat = 0 { didSet { layer?.cornerRadius = cornerRadius } }
private lazy var progressShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = progressColor.cgColor
return shapeLayer
}()
public override init(frame: NSRect = .zero) {
super.init(frame: frame)
configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
// needed because IB doesn't don't honor `wantsLayer`
open override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer = CALayer()
configure()
}
open override func layout() {
super.layout()
updateProgress()
}
open func animate(to doubleValue: Double? = nil, minValue: Double? = nil, maxValue: Double? = nil, duration: TimeInterval = 0.25) {
let currentPath = progressShapeLayer.presentation()?.path ?? progressShapeLayer.path
// stop prior animation, if any
progressShapeLayer.removeAnimation(forKey: "updatePath")
// update progress properties
if let doubleValue = doubleValue { self.doubleValue = doubleValue }
if let minValue = minValue { self.minValue = minValue }
if let maxValue = maxValue { self.maxValue = maxValue }
// create new animation
let animation = CABasicAnimation(keyPath: "path")
animation.duration = duration
animation.fromValue = currentPath
animation.toValue = progressPath
progressShapeLayer.add(animation, forKey: "updatePath")
}
}
private extension ColorfulProgressIndicator {
func configure() {
wantsLayer = true
layer?.cornerRadius = cornerRadius
layer?.backgroundColor = backgroundColor.cgColor
layer?.borderWidth = borderWidth
layer?.borderColor = borderColor.cgColor
layer?.addSublayer(progressShapeLayer)
}
func updateProgress() {
progressShapeLayer.path = progressPath
}
var progressPath: CGPath? {
guard minValue != maxValue else { return nil }
let percent = max(0, min(1, CGFloat((doubleValue - minValue) / (maxValue - minValue))))
let rect = NSRect(origin: bounds.origin, size: CGSize(width: bounds.width * percent, height: bounds.height))
return CGPath(rect: rect, transform: nil)
}
}
You can either just set its doubleValue, minValue and maxValue, or if you want to animate the change, just:
progressIndicator.animate(to: 75)
For example, below I set the progressColor and borderColor to .red, set the borderWidth to 1, set the cornerRadius to 10. I then started animating to 75, and then, before it’s even done, triggered another animation to 100 (to illustrate that animations can pick up from wherever it left off):
There are tons of ways of implementing this (so get too lost in the details of the implementation, above), but it illustrates that creating our own progress indicators is pretty easy.
You can subclass it just like any other view. But for all you're doing that is likely unnecessary.
class CustomIndicator: NSProgressIndicator {
// ...
}
As far as setting the height goes, you can do this by initializing the view with a custom frame.
let indicator = NSProgressIndicator(frame: NSRect(x: 0, y: 0, width: 100, height: 20))
There is a property called controlTint that you can set on NSProgressIndicator, but this only allows you to set the color to the predefined ones under NSControlTint. For truly custom colors, I'd recommend this route using Quartz filters via the Interface Builder.
EDIT: Although I accepted an answer that helped me get the buttons drawn correctly at runtime, I still have other issues. The root of which, I suspect, is the issue of why giving my custom button an outlet interferes with how it is drawn. I still need to know why this is. (see my answer below)
I have my own button class that extends UIButton (see below) and has several IBInspectable properties like border width/color, corner radius, and even start/end colors for a gradient background. I also use such properties for setting the image and titles' insets programmatically so I can account for various screen sizes.
Previously I had an issue where if I changed the "View as" device in the storyboard, let's say from iPhone SE to iPhone 7, and then refreshed the views and ran on a physical iPhone SE it would deploy with the insets of an iPhone 7 instead of calculating the insets with the physical device's own screen size. However, I nearly resolved this problem by overriding draw in my custom button class.
My problem now is that the overridden draw method is only called (or seems to be effective only when) the button has no outlet to the ViewController class it's placed in.
For example:
*The img is a placeholder; the insets suit the real imgs appropriately.
That bottom-right button has the gradient and corners drawn properly, where the other 3 do not. I have confirmed that adding an outlet to this button makes it behave like the rest, and removing an outlet from any of the other 3 causes it to be drawn correctly.
For reference, the outlets are just
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
immediately under the class MyViewController: UIViewController { declaration.
Also, I didn't forget to set the Custom Class in the interface builder for each button.
Here is the button class:
import UIKit
#IBDesignable
class ActionButton: UIButton {
override init(frame: CGRect){
super.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)!
setupView()
}
#IBInspectable var cornerRadius: CGFloat = 0{
didSet{
setupView()
}
}
#IBInspectable var startColor: UIColor = UIColor.white{
didSet{
setupView()
}
}
#IBInspectable var endColor: UIColor = UIColor.black{
didSet{
setupView()
}
}
#IBInspectable var borderWidth: CGFloat = 0{
didSet{
self.layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.clear{
didSet{
self.layer.borderColor = borderColor.cgColor
}
}
private func setupView(){
let colors:Array = [startColor.cgColor, endColor.cgColor]
gradientLayer.colors = colors
gradientLayer.cornerRadius = cornerRadius
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
self.setNeedsDisplay()
}
var gradientLayer: CAGradientLayer{
return layer as! CAGradientLayer
}
override func draw(_ rect: CGRect) {
//Draw image
setupView()
let btnW = self.frame.size.width
let btnH = self.frame.size.height
//Compute and set image insets
let topBtnInset = btnH * (-5/81)
let bottomBtnInset = -4 * topBtnInset
let leftBtnInset = btnW / 4 //btnW * (35/141)
let rightBtnInset = leftBtnInset
self.imageEdgeInsets = UIEdgeInsets(top: topBtnInset, left: leftBtnInset, bottom: bottomBtnInset, right: rightBtnInset)
//Compute and set title insets
let topTitleInset = btnH * (47/81)
let bottomTitleInset = CGFloat(0)
let leftTitleInset = CGFloat(-256) //Image width
let rightTitleInset = CGFloat(0)
self.titleEdgeInsets = UIEdgeInsets(top: topTitleInset, left: leftTitleInset, bottom: bottomTitleInset, right: rightTitleInset)
//Draw text
//Default font size
self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 15)
if(btnH > 81){
self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
}else if(btnH > 97){
self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
}
}
override class var layerClass: AnyClass{
return CAGradientLayer.self
}
}
Please ignore the wonky way I handled insets, font sizes, etc..
Does anyone know why specifying an outlet for the button makes it not draw the layer correctly?
A couple of thoughts:
The draw(_:) method is for stroking paths, drawing images, etc. It is for rendering the view at a given moment of time. You should not be updating layers, UIKit subviews, etc.
In your snippet, your draw(_:) is calling setupView, which then calls setNeedsDisplay (which theoretically could trigger draw(_:) to be called again). I'd suggest reserving draw(_:) for only those things that need to be manually drawn. (As it is now, you probably could completely eliminate this method as there's nothing here that is drawing anything.)
For any manual configuration of subviews (their frames, etc.), that should be moved to layoutSubviews, which is called whenever the OS determines that the subviews might need to have their frame values adjusted.
If you have any troubles with designable views, it's worth confirming that you have these in a separate target. It minimizes what needs to be recompiled when rendering in IB. It also prevents issues in the main target affecting the ability to render these designables in IB. (When Apple introduced designable views, they were very specific that one should put them in a separate target.)
I've also had intermittent problems with designable views in Xcode, but these problems are often resolved by quitting Xcode, emptying the derived data folder, and restarting.
I must confess that I do not know why adding an outlet to a designable view would affect how the view is rendered. I know that when I did the above, though, I was unable to reproduce your problem.
Anyway, I tweaked your ActionButton like so, and it appears to work for me:
#import UIKit
#IBDesignable
public class ActionButton: UIButton {
public override init(frame: CGRect = .zero) {
super.init(frame: frame)
setupView()
}
public required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupView()
}
#IBInspectable public var cornerRadius: CGFloat = 0 { didSet { setupView() } }
#IBInspectable public var startColor: UIColor = .white { didSet { setupView() } }
#IBInspectable public var endColor: UIColor = .black { didSet { setupView() } }
#IBInspectable public var borderWidth: CGFloat = 0 { didSet { layer.borderWidth = borderWidth } }
#IBInspectable public var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
private func setupView() {
let colors = [startColor.cgColor, endColor.cgColor]
gradientLayer.colors = colors
gradientLayer.cornerRadius = cornerRadius
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
private var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
public override func layoutSubviews() {
super.layoutSubviews()
let btnW = frame.width
let btnH = frame.height
//Compute and set image insets
let topBtnInset = btnH * (-5/81)
let bottomBtnInset = -4 * topBtnInset
let leftBtnInset = btnW / 4 //btnW * (35/141)
let rightBtnInset = leftBtnInset
imageEdgeInsets = UIEdgeInsets(top: topBtnInset, left: leftBtnInset, bottom: bottomBtnInset, right: rightBtnInset)
//Compute and set title insets
let topTitleInset = btnH * (47/81)
let bottomTitleInset = CGFloat(0)
let leftTitleInset = CGFloat(-256) //THIS MUST BE EQUAL TO -IMAGE WIDTH
let rightTitleInset = CGFloat(0)
titleEdgeInsets = UIEdgeInsets(top: topTitleInset, left: leftTitleInset, bottom: bottomTitleInset, right: rightTitleInset)
//Adjust text font
if btnH > 81 {
titleLabel?.font = .boldSystemFont(ofSize: 17)
} else if btnH > 97 {
titleLabel?.font = .boldSystemFont(ofSize: 20)
} else {
//Default font size
titleLabel?.font = .boldSystemFont(ofSize: 15)
}
}
public override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}
I've accepted Rob's answer because it is far more likely to be of use to anyone else, but I've found what specifically was my issue, on the off-chance someone has a similar problem.
Apparently in my code for the View Controller on which those buttons were placed I had a function I had forgot about that enables/disables those custom buttons, as well as set their background color (to a solid color). This is why those buttons looked wrong. The 4th button was never subject to mutation by this function. It was not the fact the other 3 had an outlet, but that when I removed the outlet I commented-out the call to that function.
Nevertheless, other problems were addressed in this question!
-I had a really sloppy implementation of a UIButton that overrode and called UI functions improperly, that caused problems like freezing on rotation. These were also addressed in Rob's (the accepted) answer.
-"Refresh All Views" in the Interface Builder was hanging on "Signing product", which was caused by calling self.titleLabel?.font. According to some answers on the Apple developer forums, doing this in layoutSubViews() is bad!
I've learned that we can change the UISwitch button appearance in its "on" state,
but is it also possible to change the color of the UISwitch in the "off" state?
My solution with #swift2:
let onColor = _your_on_state_color
let offColor = _your_off_state_color
let mSwitch = UISwitch(frame: CGRect.zero)
mSwitch.on = true
/*For on state*/
mSwitch.onTintColor = onColor
/*For off state*/
mSwitch.tintColor = offColor
mSwitch.layer.cornerRadius = mSwitch.frame.height / 2.0
mSwitch.backgroundColor = offColor
mSwitch.clipsToBounds = true
Result:
Try using this
yourSwitch.backgroundColor = [UIColor whiteColor];
youSwitch.layer.cornerRadius = 16.0;
All thanks to #Barry Wyckoff.
You can use the tintColor property on the switch.
switch.tintColor = [UIColor redColor]; // the "off" color
switch.onTintColor = [UIColor greenColor]; // the "on" color
Note this requires iOS 5+
Swift IBDesignable
import UIKit
#IBDesignable
class UISwitchCustom: UISwitch {
#IBInspectable var OffTint: UIColor? {
didSet {
self.tintColor = OffTint
self.layer.cornerRadius = 16
self.backgroundColor = OffTint
}
}
}
set class in Identity inspector
change color from Attributes inspector
Output
Here's a pretty good trick: you can just reach right into the UISwitch's subview that draws its "off" background, and change its background color. This works a lot better in iOS 13 than it does in iOS 12:
if #available(iOS 13.0, *) {
self.sw.subviews.first?.subviews.first?.backgroundColor = .green
} else if #available(iOS 12.0, *) {
self.sw.subviews.first?.subviews.first?.subviews.first?.backgroundColor = .green
}
Working 100% IOS 13.0 and Swift 5.0 switch both state color set same #ios13 #swift #swift5
#IBOutlet weak var switchProfile: UISwitch!{
didSet{
switchProfile.onTintColor = .red
switchProfile.tintColor = .red
switchProfile.subviews[0].subviews[0].backgroundColor = .red
}
}
The Best way to manage background color & size of UISwitch
For now it's Swift 2.3 code
import Foundation
import UIKit
#IBDesignable
class UICustomSwitch : UISwitch {
#IBInspectable var OnColor : UIColor! = UIColor.blueColor()
#IBInspectable var OffColor : UIColor! = UIColor.grayColor()
#IBInspectable var Scale : CGFloat! = 1.0
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpCustomUserInterface()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setUpCustomUserInterface()
}
func setUpCustomUserInterface() {
//clip the background color
self.layer.cornerRadius = 16
self.layer.masksToBounds = true
//Scale down to make it smaller in look
self.transform = CGAffineTransformMakeScale(self.Scale, self.Scale);
//add target to get user interation to update user-interface accordingly
self.addTarget(self, action: #selector(UICustomSwitch.updateUI), forControlEvents: UIControlEvents.ValueChanged)
//set onTintColor : is necessary to make it colored
self.onTintColor = self.OnColor
//setup to initial state
self.updateUI()
}
//to track programatic update
override func setOn(on: Bool, animated: Bool) {
super.setOn(on, animated: true)
updateUI()
}
//Update user-interface according to on/off state
func updateUI() {
if self.on == true {
self.backgroundColor = self.OnColor
}
else {
self.backgroundColor = self.OffColor
}
}
}
Swift 5:
import UIKit
extension UISwitch {
func set(offTint color: UIColor ) {
let minSide = min(bounds.size.height, bounds.size.width)
layer.cornerRadius = minSide / 2
backgroundColor = color
tintColor = color
}
}
Should you need other switches around your app, it might be also a good idea implementing #LongPham's code inside a custom class.
As others have pointed out, for the "off" state you'll need to change the background colour as well, since the default is transparent.
class MySwitch: UISwitch {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Setting "on" state colour
self.onTintColor = UIColor.green
// Setting "off" state colour
self.tintColor = UIColor.red
self.layer.cornerRadius = self.frame.height / 2
self.backgroundColor = UIColor.red
}
}
Swift 4 easiest and fastest way to get it in 3 steps:
// background color is the color of the background of the switch
switchControl.backgroundColor = UIColor.white.withAlphaComponent(0.9)
// tint color is the color of the border when the switch is off, use
// clear if you want it the same as the background, or different otherwise
switchControl.tintColor = UIColor.clear
// and make sure that the background color will stay in border of the switch
switchControl.layer.cornerRadius = switchControl.bounds.height / 2
If you manually change the size of the switch (e.g., by using autolayout), you will have to update the switch.layer.cornerRadius too, e.g., by overriding layoutSubviews and after calling super updating the corner radius:
override func layoutSubviews() {
super.layoutSubviews()
switchControl.layer.cornerRadius = switchControl.bounds.height / 2
}
In Swift 4+:
off state:
switch.tintColor = UIColor.blue
on state:
switch.onTintColor = UIColor.red
The UISwitch offTintColor is transparent, so whatever is behind the switch shows through. Therefore, instead of masking the background color, it suffices to draw a switch-shaped image behind the switch (this implementation assumes that the switch is positioned by autolayout):
func putColor(_ color: UIColor, behindSwitch sw: UISwitch) {
guard sw.superview != nil else {return}
let onswitch = UISwitch()
onswitch.isOn = true
let r = UIGraphicsImageRenderer(bounds:sw.bounds)
let im = r.image { ctx in
onswitch.layer.render(in: ctx.cgContext)
}.withRenderingMode(.alwaysTemplate)
let iv = UIImageView(image:im)
iv.tintColor = color
sw.superview!.insertSubview(iv, belowSubview: sw)
iv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
iv.topAnchor.constraint(equalTo: sw.topAnchor),
iv.bottomAnchor.constraint(equalTo: sw.bottomAnchor),
iv.leadingAnchor.constraint(equalTo: sw.leadingAnchor),
iv.trailingAnchor.constraint(equalTo: sw.trailingAnchor),
])
}
[But see now my other answer.]
2020 As of Xcode 11.3.1 & Swift 5
Here's the simplest way I've found of doing setting the UISwitch off-state colour with one line of code. Writing this here since this page is what came up first when I was looking and the other answers didn't help.
This is if I wanted to set the off state to be red, and can be added to the viewDidLoad() function:
yourSwitchName.subviews[0].subviews[0].backgroundColor = UIColor.red
Note - what this is actually doing is setting the background colour of the switch. This may influence the colour of the switch in the on-state too (though for me this wasn't a problem since I wanted the on and off state to be the same colour).
A solution for this:
Simply tie in the colours with an 'if else' statement inside your IBAction. If the switch is off, colour the background red. If the switch is on, leave the background clear so your chosen 'on' colour will display properly.
This goes inside the switch IBAction.
if yourSwitch.isOn == false {
yourSwitch.subviews[0].subviews[0].backgroundColor = UIColor.red
} else {
yourSwitch.subviews[0].subviews[0].backgroundColor = UIColor.clear
}
I found some behaviour where, upon the app resuming from background, the switch background would return to clear. To remedy this problem I simply added in the following code to set the colour every time the app comes to the foreground:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationWillEnterForeground(_:)),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
#objc func applicationWillEnterForeground(_ notification: NSNotification) {
yourSwitch.subviews[0].subviews[0].backgroundColor = UIColor.red
yourSwitch.subviews[0].subviews[0].backgroundColor = UIColor.red
}
Seems simpler than the other answers. Hope that helps!
More safe way in Swift 3 without magical 16pt values:
class ColoredBackgroundSwitch: UISwitch {
var offTintColor: UIColor {
get {
return backgroundColor ?? UIColor.clear
}
set {
backgroundColor = newValue
}
}
override func layoutSubviews() {
super.layoutSubviews()
let minSide = min(frame.size.height, frame.size.width)
layer.cornerRadius = ceil(minSide / 2)
}
}
objective c category to use on any UISwitch in project using code or storyboard:
#import <UIKit/UIKit.h>
#interface UISwitch (SAHelper)
#property (nonatomic) IBInspectable UIColor *offTint;
#end
implementation
#import "UISwitch+SAHelper.h"
#implementation UISwitch (SAHelper)
#dynamic offTint;
- (void)setOffTint:(UIColor *)offTint {
self.tintColor = offTint; //comment this line to hide border in off state
self.layer.cornerRadius = 16;
self.backgroundColor = offTint;
}
#end
XCode 11, Swift 5
I don't prefer using subViews, cause you never know when apple gonna change the hierarchy.
so I use mask view instead.
it works with iOS 12, iOS 13
private lazy var settingSwitch: UISwitch = {
let swt: UISwitch = UISwitch()
// set border color when isOn is false
swt.tintColor = .cloudyBlueTwo
// set border color when isOn is true
swt.onTintColor = .greenishTeal
// set background color when isOn is false
swt.backgroundColor = .cloudyBlueTwo
// create a mask view to clip background over the size you expected.
let maskView = UIView(frame: swt.frame)
maskView.backgroundColor = .red
maskView.layer.cornerRadius = swt.frame.height / 2
maskView.clipsToBounds = true
swt.mask = maskView
// set the scale to your expectation, here is around height: 34, width: 21.
let scale: CGFloat = 2 / 3
swt.transform = CGAffineTransform(scaleX: scale, y: scale)
swt.addTarget(self, action: #selector(switchOnChange(_:)), for: .valueChanged)
return swt
}()
#objc
func switchOnChange(_ sender: UISwitch) {
if sender.isOn {
// set background color when isOn is true
sender.backgroundColor = .greenishTeal
} else {
// set background color when isOn is false
sender.backgroundColor = .cloudyBlueTwo
}
}
I tested on IOS 14, set background as off color and onTintColor as On and works:
uiSwitch.onTintColor = UIColor.blue
uiSwitch.backgroundColor = UIColor.red
XCode 11, Swift 4.2
Starting with Matt's solution I added it to a custom, IBDesignable control. There is a timing issue in that didMoveToSuperview() is called before the offTintColor is set that needed to be handled.
#IBDesignable public class UISwitchCustom: UISwitch {
var switchMask: UIImageView?
private var observers = [NSKeyValueObservation]()
#IBInspectable dynamic var offTintColor : UIColor! = UIColor.gray {
didSet {
switchMask?.tintColor = offTintColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeObservers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeObservers()
}
private func initializeObservers() {
observers.append(observe(\.isHidden, options: [.initial]) {(model, change) in
self.switchMask?.isHidden = self.isHidden
})
}
override public func didMoveToSuperview() {
addOffColorMask(offTintColor)
super.didMoveToSuperview()
}
private func addOffColorMask(_ color: UIColor) {
guard self.superview != nil else {return}
let onswitch = UISwitch()
onswitch.isOn = true
let r = UIGraphicsImageRenderer(bounds:self.bounds)
let im = r.image { ctx in
onswitch.layer.render(in: ctx.cgContext)
}.withRenderingMode(.alwaysTemplate)
let iv = UIImageView(image:im)
iv.tintColor = color
self.superview!.insertSubview(iv, belowSubview: self)
iv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
iv.topAnchor.constraint(equalTo: self.topAnchor),
iv.bottomAnchor.constraint(equalTo: self.bottomAnchor),
iv.leadingAnchor.constraint(equalTo: self.leadingAnchor),
iv.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
switchMask = iv
switchMask?.isHidden = self.isHidden
}
}
all I finally used transform and layer.cornerRadius too.
But I have added translation to it to be center.
private func setSwitchSize() {
let iosSwitchSize = switchBlockAction.bounds.size
let requiredSwitchSize = ...
let transform = CGAffineTransform(a: requiredSwitchSize.width / iosSwitchSize.width, b: 0,
c: 0, d: requiredSwitchSize.height / iosSwitchSize.height,
tx: (requiredSwitchSize.width - iosSwitchSize.width) / 2.0,
ty: (requiredSwitchSize.height - iosSwitchSize.height) / 2.0)
switchBlockAction.layer.cornerRadius = iosSwitchSize.height / 2.0
switchBlockAction.transform = transform
}
And I did use backgroundColor and tintColor in designer.
Hope it helps.