Button frame change at runtime - ios

I have a subclass of UIButton:
class ColorButton: UIButton {
override func awakeFromNib() {
self.layer.backgroundColor = UIColor.blackColor().CGColor
self.layer.cornerRadius = frame.size.width / 2
self.clipsToBounds = true
}
}
In interface builder, I set the button with 4 constraints: width = 100, height = 100, centerX, centerY.
The button disappears when I run my code on the simulator. However, if it set
self.layer.cornerRadius = 50
it works. I cannot figure it out. If anybody understand this problem, please tell me.

Add in awakeFromNib first line:
self.layoutIfNeeded()
Code:
class ColorButton: UIButton {
override func awakeFromNib() {
self.layoutIfNeeded()
self.layer.backgroundColor = UIColor.blackColor().CGColor
self.layer.cornerRadius = frame.size.width / 2
self.clipsToBounds = true
}
}

Your code works just fine in a fresh project so I suspect the problem is somewhere else. You forgot to call super.awakeFromNib() though. From Apple docs:
You must call the super implementation of awakeFromNib to give parent
classes the opportunity to perform any additional initialization they
require. Although the default implementation of this method does
nothing, many UIKit classes provide non-empty implementations. You may
call the super implementation at any point during your own
awakeFromNib method.

Related

Swift: Apply CGSize to Button Class not changing

I am trying to add all style info into a class and then subclass a UIButton to avoid duplication of code.
At the moment, my class looks like:
class CustomButton: UIButton {
required init() {
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
frame.size = CGSize(width: 700, height: 100)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In the viewDidLoad I am adding:
let b1 = CustomButton()
view.addSubview(b1)
// auto layout
b1.translatesAutoresizingMaskIntoConstraints = false
b1.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
b1.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
As you can see in the class I have set the frame.size
frame.size = CGSize(width: 700, height: 100)
However, when I run it, It looks like so:
The width is clearly not 700. Any suggestions where I am going wrong?
The problem is you are mixing frame and auto layout. Specifically, once you turn off autoresizing, the frames go away. Why not do 100% auto layout? In fact, why not make centerX/centerY part of the init()?
class CustomButton: UIButton {
required init(width:CGFloat, height:CGFloat, centerButton:Bool) {
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
self.translatesAutoresizingMaskIntoConstraints = false
self.widthAnchor.constraint(equalToConstant: width).isActive = true
self.heightAnchor.constraint(equalToConstant: height).isActive = true
if centerButton {
self.centerXAnchor.constraint(equalTo: superview?.centerXAnchor).isActive = true
self.centerYAnchor.constraint(equalTo: superview?.centerYAnchor).isActive = true
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And change your call in viewDidLoad() to:
let b1 = CustomButton(width:700, height:100, centerButton:true)
(I added the width/height specs to init() to make your code more flexible.)
EDIT: Regards to my last (parenthesized) statement. If all you do is replace:
frame.size = CGSize(width: 700, height: 100)
with:
self.widthAnchor.constraint(equalToConstant: 700).isActive = true
self.heightAnchor.constraint(equalToConstant: 100).isActive = true
Everything will work. But, in your question you also said that (emphasis mine):
I am trying to add all style info into a class and then subclass a
UIButton to avoid duplication of code.
While the code is untested, I added parameters to the init in an effort to make the code more adaptable to creating buttons on the fly. Depending on your needs, you can extend this to everything from backgroundColor to cornerRadius.
I come from an OOP background (actually a top-down back in the 70s), so subclassing is way too intuitive to me. What I presented was just that - subclass to avoid duplication of code. Swift presents new ways - specifically extension and convenience init. I think both of these would even work for you. I'm not sure of the specific pros/cons of extension versus subclassing - my feeling is that duplication of code is about the same (technically) for both - but I'll always appreciate what a "modern" language brings to a developer's toolkit!
To decide any views position or size ,you can use either
Frames based layout or constrain based Autolayout not both together.
Thanks

How can I prevent delay of layer clipsToBounds at page transition

I have a page which has radius corners.
When the page appear, corners are not rounded at first. After seconds, corners become rounded.
I want to make corners rounded from at first.
I set properties to view at custom view's initializer.
class ModalView: UIView {
init() {
super.init(frame: .zero)
self.backgroundColor = UIColor.whiteColor()
self.clipsToBounds = true
self.layer.cornerRadius = 10
}
}
class ViewController: UIViewController {
override func loadView() {
let customView = ModalView()
customView.frame = self.view.frame
self.view = customView
}
}
This problem was solved.
The situation was like below.
image
So, I set cornerRadius to navigation controller's view.
self.navigationController?.view.layer.cornerRadius = 10
self.navigationController?.view.clipsToBounds = true
Your code does not compile.
First, initWithCoder is missing, maybe you omitted it.
Secondly, in init you call super initWithFrame, which internally is calling again init, here is where the program crashes, i don't get how yours is working but this explains the delay.
And third, loadView is used for controllers purely made in code, if you have a xib you already have a view.
Four, UIViewController does not extend a UIView, i don't even understand what you wanted to do there, you can't redefine a class.

UIButton, Autolayout and getting the height of the button

I'm trying to get the real height of my custom UIButton. But I always get a value that is way smaller than I expected. I get something around 30 but expected something about 45.
override func awakeFromNib() {
print(self.frame.height)
self.layer.cornerRadius = self.layout.frame.height / 2.0
self.clipsToBounds = true
self.titleLabel?.adjustsFontSizeToFitWidth = true
}
This is my code, but at runtime somehow autolayout changes the size, which is perfect, but I can not set the right cornerRadius (always too small). So maybe I need the multiplier or something like that. I already testet with adding the contentEdgeInsets of top and bottom, but it made no difference.
Thanks
Tobias
The view hasn't been laid out in awakeFromNib(). Set your corner radius when layout has guaranteed to have happened.
For a view:
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height / 2.0
}
For a UIViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layer.cornerRadius = view.frame.height / 2.0
}
Try calling self.layoutIfNeeded() before you print the size

Xcode 7: Shadow does NOT show around UIView in Interface Builder

I want to see my UIView with added shadow like the example image below in storyboard. Unfortunately, there isn't an existing option to set it. Is there a way to set up the shadow using keypath so I can view the shadow in storyboard?
EDIT:
I have tried to render a shadow with the following code:
import Foundation
import UIKit
#IBDesignable
class ShadowedView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let shadowPath = UIBezierPath(rect: self.bounds)
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.blackColor().CGColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowOpacity = 0.2
self.layer.shadowPath = shadowPath.CGPath
self.clipsToBounds = false
}
}
#IBDesignable allowed me to see the changes immediately, but the shadow still is not present in the storyboard. Here is a screenshot of it:
And the result should be the example image. Thank you!
Image credit to #Wezly
You would need to override drawRect: or prepareForInterfaceBuilder depending on your needs. You can read more in the docs : https://developer.apple.com/library/ios/recipes/xcode_help-IB_objects_media/Chapters/CreatingaLiveViewofaCustomObject.html
Bottom line : you will have to add the code you posted in the drawRect: method in your custom view annotated as IBDesignable and Interface Builder will render it for you.

Best place to set cornerRadius based on UIButton size in subclass?

So I will be having a few different rounded buttons within an app (all the way to circular buttons), and from what I can tell, the easiest way to achieve this is to set the cornerRadius property of the buttons CALayer.
However, I don't want to be doing this manually for every button that requires it in every controller, so I thought a simple subclass that sets this on init would be the way.
I am using Storyboard and Autolayout to position and size the buttons, and assigning them to this subclass.
class RoundedButton: UIButton {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.cornerRadius = self.bounds.size.height / 2.0
self.clipsToBounds = true
NSLog("BUTTON BOUNDS: H-%f W-%f", self.bounds.size.height, self.bounds.size.width)
NSLog("BUTTON FRAME: H-%f W-%f", self.frame.height, self.frame.width)
}
}
But I have come to find out that at this point (i.e. init), the size of neither the frame nor bounds are final. For a button in Storyboard sized (and constrained) to H40 x W40, the bounds/frame sizes are showing H30 x W38.
This means that cornerRadius doesn't get the value I expect.
I have confirmed that at some later point (e.g. when the button can already respond to a tap) that its frame/bounds are indeed H40 x W40.
So after all that, my question is, within the UIButton subclass, when/where can I safely set cornerRadius using the final frame/bounds values of the instance?
If you want your code to be executed after the view has gone through AutoLayout you must do it in layoutSubviews after calling super.layoutSubviews().
Like this :
class RoundedButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.height / 2.0
clipsToBounds = true
}
}
This code is not perfect though because it doesn't support when height is bigger than width (easy to fix though…).
Try this in Button class.
override func layoutSubviews() {
super.layoutSubviews()
// here...
}
Since you wish to use initWithCoder rather than initWithFrame (where you do have the frame struct), you can override the layoutSubviews method.
When layoutSubviews is called the frame is correct.
In objective-c you can write
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.bounds.size.height/2;
}

Resources