draw func doesn't work for button from storyboard - ios

In my app I have several UIButtons, which I want to be with rounded corners. All of these buttons are placed in Storyboard.
I've created custom class RoundedButton, which has simple override of draw func.
import UIKit
class RoundedButton: UIButton {
override func draw(_ rect: CGRect) {
self.layer.cornerRadius = 4.0
}
}
I've added this custom class to every button in Storyboard, but buttons corners are not rounded in simulator.
Thanks for any help!

draw method is not the right place to add this piece of code.
You should move it to awakeFromNib
Coming to your question, add self.layer.masksToBounds = true after setting cornerRadius. The rounded corner should then show up.

You should instead override init
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
layer.cornerRadius = 4.0
}
EDITED:
if you don't use storyboard, or xib's
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 4.0
}

Related

Custom UIView Implemenation

I have customView called BaseView which has a contentView, In the contentView I am adding all other subviews(UILabel, UIButton, etc) in the override init(frame: CGRect) method.
Now I have 10 subclasses of my BaseView, which also overriding init(frame: CGRect) and calling base class init(frame: CGRect) method.
Here all my subclasses looks similar UI to its BaseView, Now there is one subclass of BaseView doesn't want some of the UI Elements in this base class, but I still need to call superview init(frame: CGRect). How do I change the code without affecting other classes?
Class BaseView: UIView {
let contentView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
let lbl1 = UILabel()
contentView.addSubView(lbl1)
let lbl2 = UILabel()
contentView.addSubView(lbl2)
self.addSubView(contentView)
}
Class subView1: BaseView {
override init(frame: CGRect) {
super.init(frame: frame)
// This class will show lbl1, lbl2 and lbl3 in the contentview
let lbl3 = UILabel()
contentView.addSubView(lbl3) // this contentview is BaseView's ContentView
}
// Similarly I have around 10 Subclasses of BaseView which is adding some UI Element to
baseview's contentView
// Question here is, below I am going to create another subclass of BaseView, But I don't
want to show lbl1 and lbl2 which is created in my BaseView's contentview
Class myView: BaseView {
// this class should not show the base class uilement lbl1 and lbl2, It should show only
lbl4 which is created by this class only
override init(frame: CGRect) {
super.init(frame: frame)
let lbl4 = UILabel()
contentView.addSubView(lbl4) // this contentview is BaseView's ContentView
}
How do I change the code without affecting other classes?
You don't. A different approach is needed. Three options are:
Don't derive the view in question from BaseView, and just reproduce whatever BaseView functionality you need.
Continue to derive from BaseView, and hide or remove the elements you don't need after BaseView's initialization method. The success of this plan will depend on how tolerant BaseView is when elements it expects are missing.
Refactor the functionality in BaseView into some new class that has the behavior that's common to all your views, and have your one outlier view and BaseView each descend from that new class.
Here is a different approach to implementing the required functionality.
Create a parent class with a content view where you want to show the content.
class SuperView: UIView {
let contentView = UIView()
override init(frame: CGRect) {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
Inherit BaseView class from SuperView class and you can inherit other classes from BaseView class.
class BaseView: SuperView {
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(UILabel()) // UILabel 1
contentView.addSubview(UILabel()) // UILabel 2
self.addSubview(contentView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
Inherit SubView class from SuperView class to add different properties and functions that you want. Here you don't need to inherit from BaseView class.
class SubView: SuperView {
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(UILabel()) // UILabel 3
self.addSubview(contentView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
I hope it will help you to implement the required functionality as you want.

Transparent view is black?

I'm trying to create a view that is transparent in certain spots to see an image behind. However, for some reason in the transparent part of the view, I'm seeing black, instead of what's behind the view. I've trimmed it down to very little code and don't understand why my transparent view shows black instead of red (the color of the view behind). Here's my code:
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let redView = UIView(frame: view.frame)
redView.backgroundColor = UIColor.red
let transparentView = TransparentView(frame: view.frame)
view.addSubview(redView)
view.addSubview(transparentView)
}
}
class TransparentView: UIView {
override func draw(_ rect: CGRect) {
UIColor.clear.setFill()
UIRectFill(rect)
}
I would expect the screen to be full red, but instead it shows full black. Before someone says it's a lot easier to make a clear view, I'm actually trying to do more complex things in drawRect, just dropped down to the most basic thing to try to debug my problem. What am I missing here?
Use self.isOpaque = false; to make the view/layer transparent even when drawRect is overriden.
class TransparentView: UIView {
override init(frame: CGRect) {
super.init(frame: frame);
self.isOpaque = false; //Use this..
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
UIColor.clear.setFill()
UIRectFill(rect)
}
}
I figured it out. Apparently even if you override draw, backgroundColor seems to still be considered, and defaults to black. I added the following to my transparent view class:
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
"drawRect: Implement this method if your view draws custom content. If your view does not do any custom drawing, avoid overriding this method." Link
That been said would be better as you said just set background color on Init.
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Personally i wont subclass a view for so little customization. Just set it while creating it. Also view setup is better on viewDidLoad, not in viewWillAppear. Since it will execute every time your view will go foreground and u will end with two transparent views added.
Also keeping those line in a extension with a private function helps to keep your code clear.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
}
//MARK: - Private Methods
extension ViewController{
fileprivate func setupViews(){
let redView = UIView(frame: view.frame)
redView.backgroundColor = UIColor.red
view.addSubview(redView)
let transparentView = UIView(frame: view.frame)
transparentView.backgroundColor = UIColor.clear
view.addSubview(transparentView)
}
}
Please notice that a more clear approach would be to create those Views in the Storyboard (not by code). Keep code clear and its easier to understand and see whats going on.

#IBDesignable not showing background color in IB

I have a UIView as below:
import UIKit
#IBDesignable
class CHRAlertView: UIView {
#IBOutlet var icon:UILabel!
#IBOutlet var alertText:UITextView!
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
private func initialize(){
self.backgroundColor = UIColor.green
}
}
Based on how #IBDesignable works, this should show up in IB with a green background, but I get the clear color like this:
Why is this not functioning as expected? I need the background color to show in IB based on the defaults set in my #IBDesignable.
Since backgroundColor is an IB property not created via #IBInspectable, it always seems to overwrite whatever is in the init or draw methods. Meaning, if it is "default" in IB, it causes it to be overwritten with nil. However, if set in the prepareForInterfaceBuilder method backgroundColor works and shows in IB. So, the backgroundColor, it can be reasonably assumed, must be set at runtime. To do this I have the below:
//------------------
//Setup and initialization
//------------------
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
//Setups content, styles, and defaults for the view
private func initialize(){
self.staticContent()
self.initStyle()
}
//Sets static content for the view
private func staticContent() {
}
//Styles the view's colors, borders, etc at initialization
private func initStyle(){
}
//Styles the view for variables that must be set at runtime
private func runtimeStyle(){
if self.backgroundColor == nil {
self.backgroundColor = UIColor.green
}
}
override func prepareForInterfaceBuilder() {
self.runtimeStyle()
}
override func awakeFromNib() {
super.awakeFromNib()
self.runtimeStyle()
}
This defaults the backgroundColor if it is "default" (read nil) in IB to a color, but does not use the UIColor.green if a backgroundColor is set in IB, which is exactly what I need.
Shoutout to Eridius in the #swift-lang irc for helping me get to this answer.

Save styled element to reuse in other view controllers

I'm making an iOS app with Xcode where all my buttons should have the same style. The only difference between these buttons are their height and width. Is there a way to save the first one I styled, and then use it again in the different view controllers, without copying? I'm thinking if this is possible it'll save me a lot of time.
Applying the same style to multiple instances of UIButton:
Strictly Programmatic route:
The first two methods are what I would do. The third is only to illustrate that it is possible to write an init that copies settings from another button.
Apply preset style with a sub class:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blackColor()
// more styling
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Apply preset style with an extension to UIButton:
extension UIButton {
func setCustomStyle1() {
self.backgroundColor = UIColor.blackColor()
// nore styling
}
}
Copy style with a convenience init in an extension to UIButton:
extension UIButton {
convenience init(styleFromButton button: UIButton, frame: CGRect) {
self.init(frame: frame)
self.backgroundColor = button.backgroundColor
}
}
Interface Builder solution:
Create a new Swift file:
Create a sub class of UIButton in the new file:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
style()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
style()
}
private func style() {
self.backgroundColor = UIColor.blackColor()
// more styling
}
}
Go back to the Interface Builder and select a UIButton you want to style.
Select the third panel on the right, this is the identity inspector.
Select your sub class as the class for the UIButton.
Repeat for all buttons to style.
Or style the entire thing in IB and Alt-Drag to make a copy.

How to custom this image tab in UIView,using #IBDesignable and #IBInspectable is better

How to draw the following tab image in UIView?The text is changeable,which implies that the image could stretch in width. And I know in XCode6,it supports live render.So I think if possible,it's color,text,and size could be set in attributes inspector.
You have to create a custom class, based on UIView. This class is declared as #IBDesignable and has #IBInspectable properties. Override UIView.drawRect() and you are totally free on how your view gets displayed.
Here is a sample class to get you started.
import UIKit
#IBDesignable
class MyTabView: UIView {
#IBInspectable tabTitle: String = ""
#IBInspectable tabColor: UIColor = UIColor.clearColor()
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
// stuff for interface builder only
}
override func drawRect(rect: CGRect)
{
// this is where your view gets drawed
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
}
}

Resources