I've make a custom class for UIButton but in small screen devices e.g. iPhone 5s cornerRadius not working properly
You've to looks closer to see the UiButton's cornerRadius is not perfectly rounded
class customRoundButton: UIButton
{
override func awakeFromNib()
{
self.layer.cornerRadius = (self.layer.frame.height / 2)
self.layer.borderColor = fontColor.defualtBlue.cgColor
self.layer.borderWidth = 1
self.layer.clipsToBounds = true
self.layer.layoutIfNeeded()
}
}
Setting the cornerRadius in awakeFromNib is too early. Use layoutSubviews instead:
class CustomRoundButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
layer.borderColor = UIColor.blue.cgColor
layer.borderWidth = 1
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = layer.frame.height / 2
}
}
Try
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.bounds.height * 0.50
}
So by the time your custom UIButton is going to be added to the subviews it will know it bounds and you just have to assign the corner radius to be the half of bounds height.
Better use IBDesignable for this :
#IBDesignable
class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 3.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setupView()
}
override func awakeFromNib() {
super.awakeFromNib()
self.setupView()
}
func setupView() {
self.layer.masksToBounds = false
self.layer.cornerRadius = cornerRadius
}
}
I stumbled upon this question here and found for me what worked best in Swift 5 is setting the corner radius in awakeFromNib inside Disptatch.main.async When using layoutSubviews or setNeedsLayout the closure is called for every little change or movement. This might work for some use cases. I have not tried that with the above example.
My code looks like this:
override func awakeFromNib() {
super.awakeFromNib()
imageView.layer.masksToBounds = false
imageView.clipsToBounds = true
DispatchQueue.main.async {
self.imageView.layer.cornerRadius = self.imageView.bounds.height / 2.0
}
}
Related
I've a switch inside table which I'm creating programmatically. I can't change switch's off border colour to gray. I tried tint colour which isn't working either.
How to fix it?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
fatalError("...")
}
//...
let switchView = UISwitch()
switchView.layer.borderColor = UIColor.greyColour.cgColor
cell.accessoryView = switchView
return cell
}
You didn't specify what effect you want to achieve but for layer.borderColor to work you need to setup layer.borderWidth also. However, because switch layer is rectangular it will look like this:
Which might be not what you want. So to make the border follow the switcher's shape you'll need to modify its corner radius:
switchView.layer.borderColor = UIColor.gray.cgColor
switchView.layer.borderWidth = 1.0
switchView.layer.cornerRadius = 16.0
to make it looks like this:
Update
If you want to apply border only for switcher off state it'll be a bit more tricky because you need to handle switcher states changes. The easiest way I could think of is to subclass UISwitch and provide your own behaviour by overriding sendActions method:
class BorderedSwitch: UISwitch {
var borderColor: UIColor = UIColor.gray {
didSet {
layer.borderColor = borderColor.cgColor
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override var isOn: Bool {
didSet {
updateState()
}
}
override func sendActions(for controlEvents: UIControl.Event) {
super.sendActions(for: controlEvents)
if controlEvents.contains(.valueChanged) {
updateState()
}
}
private func setup() {
layer.borderColor = borderColor.cgColor
layer.cornerRadius = frame.height / 2
layer.borderWidth = 1.0
}
private func updateState() {
layer.borderWidth = isOn ? 0.0 : 1.0
}
}
Notice that I also updated cornerRadius value to frame.height / 2 to avoid magic numbers
If you add a switch with code, it looks just like a switch you add in a storyboard. Neither way of creating a switch has a border color.
The code below adds a switch to a view controller's content view:
#IBOutlet var switchView: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
switchView = UISwitch()
switchView.translatesAutoresizingMaskIntoConstraints = false //Remember to do this for UIViews you create in code
if false {
//Add a gray border to the switch
switchView.layer.borderWidth = 1.0 // Draw a rounded rect around the switchView so you can see it
switchView.layer.borderColor = UIColor.gray.cgColor
switchView.layer.cornerRadius = 16
}
//Add it to the container view
view.addSubview(switchView)
//Create center x & y layout anchors (with no offset to start)
let switchViewXAnchor = switchView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
switchViewXAnchor.isActive = true
let switchViewYAnchor = switchView.centerYAnchor.constraint(equalTo: view.centerYAnchor,
constant: 0.0)
switchViewYAnchor.isActive = true
}
It looks perfectly normal.
So, I have this custom UITextField and I have two methods to add CALayer and remove the CALayer but remove is not working.
#IBDesignable class AppTextField : UITextField {
private let bottomLine = CALayer()
override func layoutSubviews() {
self.font = .systemFont(ofSize: 20)
self.addBottomLine()
self.clearButtonMode = .unlessEditing
super.layoutSubviews()
}
func removeBttomLine() {
bottomLine.removeFromSuperlayer()
}
private func addBottomLine() {
bottomLine.frame = CGRect(origin: CGPoint(x: 0, y: self.frame.height + 4), size: CGSize(width: self.frame.width, height: 1))
bottomLine.backgroundColor = UIColor.init(hexString: "#DCCFCA")?.cgColor
self.borderStyle = .none
self.layer.addSublayer(bottomLine)
}
}
The only thing you should do in layoutSubviews() is update frames as necessary.
This will show the red line when the field is NOT being edited, and will remove it while the field IS being edited:
#IBDesignable class AppTextField : UITextField {
private let bottomLine = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.font = .systemFont(ofSize: 20)
self.backgroundColor = .white
self.clearButtonMode = .unlessEditing
self.borderStyle = .none
bottomLine.backgroundColor = UIColor.red.cgColor
addBottomLine()
}
override func layoutSubviews() {
super.layoutSubviews()
var r = bounds
r.origin.y = bounds.maxY
r.size.height = 4.0
bottomLine.frame = r
}
func removeBottomLine() {
bottomLine.removeFromSuperlayer()
}
private func addBottomLine() {
self.layer.addSublayer(bottomLine)
}
override func resignFirstResponder() -> Bool {
super.resignFirstResponder()
addBottomLine()
return true
}
override func becomeFirstResponder() -> Bool {
super.becomeFirstResponder()
removeBottomLine()
return true
}
}
The reason could because the layoutSubviews method gets called multiple times and the layer is getting added multiple times. Try moving the addBottomLine method to required init?(coder:) method if you're using storyboard or use init(frame:) or custom init whichever gets called just once. Here's an example:
#IBDesignable class AppTextField : UITextField {
required init?(coder: NSCoder) {
super.init(coder: coder)
addBottomLine()
}
}
You're doing 3 things in your add method:
adusting frame
setting color
adding as sublayer
And because you're calling it from layoutSubviews it's called multiple times and you're ending with multiple layers added and that's why calling remove doesn't seem to work.
To make this code work you should move adding part to init (withCoder, withFrame or both). You can join it with setting color because it can be done once. Next part is adjusting frame in layoutSubviews which is required because layers can't into autolayout. At the end you will have creation, adding as sublayer and set part called once at init, and adjusting called multiple times on layout pass. Now remove when called once - it would work with visible effect this time.
In a UIView subclass, I have the following property override for override var bounds: CGRect:
#IBDesignable
class GEView: UIView {
private var shadowView: UIView? {
didSet {
guard let superview = superview else { return }
guard let shadowView = self.shadowView else { return }
// Add the shadow to the superview, as the shadow cannot
// also allow rounded corners simultaneously
superview.addSubview(shadowView)
shadowView.layer.zPosition = layer.zPosition - 1
shadowView.edges(to: self)
}
}
// CALLED WHEN SETTING #IBInspectable PROPERTIES
/// Creates a shadow if one has not yet been created.
private func createShadowIfNeeded() {
guard shadowView == nil else { return }
shadowView = UIView()
shadowView?.layer.shadowPath = UIBezierPath(roundedRect: bounds,
cornerRadius: cornerRadius).cgPath
shadowView?.layer.shouldRasterize = true
}
// THE PROPERTY TO ATTEMPT THE SHADOW MOVING
override var bounds: CGRect {
didSet {
shadowView?.layer.shadowPath = UIBezierPath(roundedRect: bounds,
cornerRadius: cornerRadius).cgPath
}
}
}
The attempt was to re-draw the shadow many times as the bounds change when the view constraints are animated (leading to the view changing size).
However, the bounds change instantly, as the animation is just visual. Is there a way I can get this shadow to follow the view as it animates? It would be better if this can be in the UIView subclass instead of the animation block, which is UIView.animate.
Here is what the problem looks like:
I want the shadow to follow along whilst the view moves. At the end of the gif, the shadow position and view position are correct, because the override ignores animations and pretends it has already animated.
How can I fix this?
Try updating the shadow in layoutSubviews() of CustomView, i.e.
class CustomView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.shadowRadius = 10.0
self.layer.shadowOpacity = 1.0
self.layer.shadowColor = UIColor.black.cgColor
let oldPath = self.layer.shadowPath
let newPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 0.0).cgPath
if oldPath != nil {
let shadowPathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowPathAnimation.fromValue = oldPath
shadowPathAnimation.toValue = newPath
self.layer.add(shadowPathAnimation, forKey: "shadowAnimation")
self.layer.shadowPath = newPath
}
}
}
class ViewController: UIViewController {
#IBOutlet weak var customView: CustomView!
#IBOutlet weak var trailingConstraint: NSLayoutConstraint!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 3.0) {
self.trailingConstraint.constant = 200.0
self.view.layoutIfNeeded()
}
}
}
I have a UITextField, I would like it having a height & round corner & a custom border color. I tried:
Select
Create an #IBOutlet for TextField:
#IBOutlet weak var messageInputField: UITextField!
In viewWillAppear , I have:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
messageInputField.layer.borderColor = UIColor(red:0.95, green:0.71, blue:0.01, alpha:1).cgColor
messageInputField.borderStyle = UITextBorderStyle.roundedRect
}
The output is:
The round corner works, but where is the color I specified in code? How to set the color I want for the border ?
You have to add messageInputField.layer.borderWidth = 1.0 or whatever borderWidth you'd like...
Note that you can't use roundedRect if you want to use a custom border. For a custom rounded border do the following:
Set the textfield's borderStyle property to none
Add a custom border by setting the textfield's layer's borderColor and borderWidth
To add rounded corners set the textfield's layer's cornerRadius to a value you wish
Override the textfield's intrinsicContentSize (or add a height constraint) to determine a default height
Override the textfield's textRect and editingRect to add correct insets
Example:
#IBDesignable
class CustomRoundedTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
private func sharedInit() {
borderStyle = .none
layer.borderColor = UIColor.orange.cgColor
layer.borderWidth = 2
layer.cornerRadius = 8
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: 30)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return super.textRect(forBounds: bounds).insetBy(dx: 8, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return super.editingRect(forBounds: bounds).insetBy(dx: 8, dy: 0)
}
}
I added a subView to my customized UIButton. Then I created a button subclass to my customized UIButton from Storyboard, but the bounds of the subView wasn't set correctly.
Expected:
What I got instead:
Code of my customized UIButton, quite simple:
import UIKit
#IBDesignable
class RoundShadowButton: UIButton {
var backLayer: UIView!
override func drawRect(rect: CGRect) {
setup()
}
func setup() {
backLayer = UIView()
//add the subView
backLayer?.bounds = bounds
backLayer!.layer.cornerRadius = 10.0
backLayer?.layer.masksToBounds = false
backLayer!.layer.borderColor = UIColor.redColor().colorWithAlphaComponent(0.3).CGColor
backLayer!.layer.borderWidth = 1.0
backLayer.clipsToBounds = false
addSubview(backLayer)
//add shadow to layer of UIButton
layer.shadowColor = UIColor.blackColor().CGColor
imageEdgeInsets = UIEdgeInsetsMake(9, 32, 9, 32)
layer.shadowOffset = CGSizeMake(0.0, 0.5)
layer.shadowRadius = 1.0
layer.shadowOpacity = 0.7
layer.masksToBounds = false
clipsToBounds = false
}
How can I fix the problem?
You will override method "func layoutSubviews()" to set frame of your backLayer subview, something like this
override func layoutSubviews()
{
super.layoutSubviews()
backLayer.frame = bounds
}
Why did you do setup in "drawRect(rect: CGRect)" no in the "init?(coder aDecoder: NSCoder)" or "func awakeFromNib()"?