I want to pass array of colors into drawRect in swift, how can I do that? (I'm getting alot of errors..)
class GradientColorView : UIView {
static let colors : NSArray = NSArray()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
class func initWithColors(colors :NSArray) {
}
override func drawRect(rect: CGRect) {
println(self.colors)
println("drawRect has updated the view")
}
}
Your class has color as static variable which is just like a class variable and it is let which means that is is immutable constant. You would need to change that let to var if you want it to be modifiable. So, you cannot access that from instance. I would propose you to change that to instance variable which makes it easy to do drawing call when colors changes.
You could do something like this,
class GradientColorView : UIView {
var colors : NSArray = NSArray() {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
class func initWithColors(colors :NSArray) {
}
override func drawRect(rect: CGRect) {
println(self.colors)
println("drawRect has updated the view")
}
}
Then you can update the colors from the instance of gradientView which will redraw it again,
let gradientView = GradientColorView(frame: CGRectMake(0, 0, 200, 200))
gradientView.colors = [UIColor.redColor(), UIColor.orangeColor(), UIColor.purpleColor()]
Related
I have a UITextView custom class:
class TitleTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
textColor = .brand100
backgroundColor = .clear
isUserInteractionEnabled = false
textAlignment = .left
isScrollEnabled = false
let frameWidth = Constants.screenSize.width * 87.5 / 100
font = UIFont.OpenSans(.semibold, size: (frameWidth * 8.55 / 100))
}
}
I used this text view custom class inner a UIView.
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
And I called this UIView in a UIViewController.
private func setupTitleView() {
let titleView = UINib(nibName: "TitleView", bundle: .main).instantiate(withOwner: nil, options: nil).first as! UIView
titleView.frame = contentHeaderView.bounds
contentHeaderView.addSubview(titleView)
view.layoutIfNeeded()
}
But when I set the textColor property in my custom UIView (MyCustomHeaderView) the color doesn't change.
Do you have any idea about why the reason that my UITextView doesn't apply the color that I set in my custom UIView?
I called layoutIfNeed() but this doesn't work.
It's because you are doing everything inside the layoutSubviews Which in itself is really bad practice.
In your case you instantiate the CustomHeaderView and the layout for that is called, hence calling layoutSubviews next step is that the textView is added to your CustomHeaderView and then the textView's layoutSubviews is called and will override your color.
You can solve this in two ways i believe. Altho i don't work with Nibs and storyboards,
first:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
Second, this is a big maybe:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
defer {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
}
Defer will wait till everything is been initialised before running whatever is in the block. I don't know tho how that works with layoutSubviews
I have created a viewcontroller which consists of two classes. These are displayed below:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var Viewholder: UIImageView!
var txt:String?
override func viewDidLoad() {
super.viewDidLoad()
let vv= TestView(frame: Viewholder.frame)
view.addSubview(vv)
txt= "hello"
let rr = TestView(frame: self.view.frame,textvalue:txt!)
rr.colour = "" // set value
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class TestView: UIView {
var textvalue:String?
init(frame: CGRect,textvalue) {
self.textvalue= textvalue
super.init(frame: frame)
self.sharedLayout()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.setupPaths()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func sharedLayout() {
print(textvalue!) // works here
}
func setupPaths() {
print(textvalue!) // doesnt work here (displays nil)
}
How can I make it work so that the value of textvalue in "setupPaths" gives the correct value so that I can then changee the text of a label. I am having trouble getting the setuppath function to display the passed value as it is returning null. This is stopping me from editting the label with a passed value.
You can create a customView like this
class TestView: UIView {
var colour:String?
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.sharedLayout()
}
init(frame: CGRect,colour:String) {
self.colour = colour
super.init(frame: frame)
self.sharedLayout()
}
func sharedLayout() {
}
}
//
You can create it like this
let rr = TestView(frame: self.view.frame,colour:"test")
// rr.colour = "" // set value
You are badly confused about object-oriented programming. In your code:
let vv= TestView(frame: Viewholder.frame)
view.addSubview(vv)
txt= "hello"
let rr = TestView(frame: self.view.frame,textvalue:txt!)
The let vv line creates a TestView object and installs it as a subview of the view controller's content view.
Then the next 2 lines create a NEW instance of TestView and install text into that 2nd view. You then forget about this second view.
This is like buying a new car, setting the station on the radio of the new car, abandoning the new car, and then going home and wondering why the radio station on your existing car didn't change. The two objects are not the same object and settings in one instance of TestView have no effect on the other instance of TestView.
I know must call a designated initializer of the superclass, I think init(type: UIButtonType) had called a designated initializer, so I used it in subclass convenience initializer, but failed
class TSContourButton: UIButton {
enum ContourButtonSizeType {
case large
case small
}
convenience init(type:ContourButtonSizeType) {
self.init(type: .custom)
}
then, I tried this. It compiles okay. but, It doesn't look professional
class TSClass: UIButton {
convenience init(frame: CGRect, myString: String) {
self.init(frame: frame)
self.init(type: .custom)
}
so, I doubt that I may think wrong. So, I did some test. It successfully called super convenience initializer. Why I can't use self.init(type: .custom) in convenience initializer at my subclass of UIButton?
class person: UIButton {
var name: String = "test"
override init(frame: CGRect) {
super.init(frame: .zero)
self.name = "one"
}
convenience init(myName: String) {
self.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class man: person {
convenience init(mySex: Int) { // it successfully call superclass convenience initializer
self.init(myName: "info")
}
If, for say, name is your mandatory field, you implement all your initial set up in a function. And you should handle if name is not available. I'll keep small as the default option if no type was provided.
// MARK:- Designated Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup(type: .small)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialSetup(type: .small)
}
// MARK:- Convenience Initializers
convenience init(type: ContourButtonSizeType) {
self.init(frame: .zero)
initialSetup(type: type)
}
func initialSetup(type: ContourButtonSizeType) {
// handle all initial setup
}
init(type: UIButtonType) is not UIButton's designated initializer, init(frame: CGRect) is the right designated initializer of UIButton
you just need to overwrite init(frame: CGRect)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
if you want to customize your own button, you can add convenience init(myType: UIButton.ButtonType)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
let button2 = MyButton(myType: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
// 创建自己的遍历构造器
// this is what you want
convenience init(myType: UIButton.ButtonType) {
self.init(type: myType) // called the convenience initializer which you automatically inherit from super class
// customize your own button
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If this is useful to you, can you give me a like, (づ ̄ 3 ̄)づ
Utilizing constants in subclasses with lot of initializers to override is tedious. Look at the class below, I need to duplicate the code in both initializers.
class Test : UIView {
let subview: UIView
override init(frame: CGRect) {
subview = UIView() // once
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
subview = UIView() // twice
super.init(coder: aDecoder)
}
}
If I try to make use of a common initializer then I get the following errors (see the comments)
override init(frame: CGRect) {
commonInit() // 1: Use of 'self' in method call 'commonInit' before super.init initializes self
super.init(frame: frame) // 2: Property 'self.subview' is not in initialized at super.init call
}
private func commonInit() {
subview = UIView() // 3: Cannot assign to 'subview' in 'self'
}
It works fine if I do not use a constant and define the subview like:
var subview: UIView?
And then of course switch order in init like this:
super.init(frame: frame)
commonInit()
So my question: is there no way to use a common initializer for constants in Swift as of now?
EDIT: I totally forgot to mention that the struggle here is that I can't initiate the subview before I'm in the init, it's initiated based on data that is not known when declaring the constant.
Try this:
class Test : UIView {
let subview = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Another option:
class Test : UIView {
let subview:UIView
init(frame: CGRect?, coder: NSCoder?) {
// The first phase initialization here
subview = UIView()
if let frame = frame {
super.init(frame: frame)
}
else if let coder = coder {
super.init(coder: coder)
}
else {
super.init()
}
// the Second phase initialization here
self.addSubview(subview)
}
convenience init() {
self.init(frame: nil, coder: nil)
}
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)
}
required convenience init(coder aDecoder: NSCoder) {
self.init(frame: nil, coder: aDecoder)
}
}
A little bit cleaner alternative:
class Test : UIView {
let subview:UIView
private enum SuperInitArg {
case Frame(CGRect), Coder(NSCoder), None
}
private init(_ arg: SuperInitArg) {
subview = UIView()
switch arg {
case .Frame(let frame): super.init(frame:frame)
case .Coder(let coder): super.init(coder:coder)
case .None: super.init()
}
addSubview(subview)
}
convenience init() {
self.init(.None)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))
}
required convenience init(coder aDecoder: NSCoder) {
self.init(.Coder(aDecoder))
}
}
Do the following:
class Test : UIView {
let subview = UIView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//edit subview properties as needed
}
}
This works well for me:
// Declare this somewhere (it can be used by multiple classes)
class FrameCoder: NSCoder {
let frame: CGRect
init(_ frame: CGRect) {
self.frame = frame
super.init()
}
}
Then, when you want a common initializer pattern, use this:
class MyView: UIView {
let something: SomeType
required init(coder aDecoder: NSCoder) {
if (aDecoder is FrameCoder) {
super.init(frame: (aDecoder as! FrameCoder).frame)
}
else {
super.init(coder: aDecoder)
}
// Common initializer code goes here...
something = // some value
}
override init(frame: CGRect) {
self.init(coder: FrameCoder(frame))
}
}
The advantage of using this method is you don't need to create default values for let definitions -- you can set them to the correct values in context, just like you would if there were only a single initializer.
Note that you can use this technique for initializers taking arbitrary values (not just for init(frame: CGRect)) -- you can create a specific NSCoder subclass to wrap any value and type you need to pass to an initializer, and then chain it into your init(coder:) method.
(also, there's probably some way to do this with a generic... haven't quite figured that out yet! Anyone...?)
One option is following the Xcode pattern:
class Test : UIView {
var subview: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
subview = UIView()
}
}
Notice your subview is a var
Another option is:
class Test : UIView {
let subview: UIView = {
let sv = UIView()
// some config, (i.e.: bgColor etc., frame is not yet _real_
// can't yet access instance's frame and other properties
return sv
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
// frame might be valid, preferably use layout constraints
addSubview(subview)
}
}
Hope this helps
Say I want to init a UIView subclass with a String and an Int.
How would I do this in Swift if I'm just subclassing UIView? If I just make a custom init() function but the parameters are a String and an Int, it tells me that "super.init() isn't called before returning from initializer".
And if I call super.init() I'm told I must use a designated initializer. What should I be using there? The frame version? The coder version? Both? Why?
The init(frame:) version is the default initializer. You must call it only after initializing your instance variables. If this view is being reconstituted from a Nib then your custom initializer will not be called, and instead the init?(coder:) version will be called. Since Swift now requires an implementation of the required init?(coder:), I have updated the example below and changed the let variable declarations to var and optional. In this case, you would initialize them in awakeFromNib() or at some later time.
class TestView : UIView {
var s: String?
var i: Int?
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I create a common init for the designated and required. For convenience inits I delegate to init(frame:) with frame of zero.
Having zero frame is not a problem because typically the view is inside a ViewController's view; your custom view will get a good, safe chance to layout its subviews when its superview calls layoutSubviews() or updateConstraints(). These two functions are called by the system recursively throughout the view hierarchy. You can use either updateContstraints() or layoutSubviews(). updateContstraints() is called first, then layoutSubviews(). In updateConstraints() make sure to call super last. In layoutSubviews(), call super first.
Here's what I do:
#IBDesignable
class MyView: UIView {
convenience init(args: Whatever) {
self.init(frame: CGRect.zero)
//assign custom vars
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
private func commonInit() {
//custom initialization
}
override func updateConstraints() {
//set subview constraints here
super.updateConstraints()
}
override func layoutSubviews() {
super.layoutSubviews()
//manually set subview frames here
}
}
Swift 5 Solution
You can try out this implementation for running Swift 5 on XCode 11
class CustomView: UIView {
var customParam: customType
var container = UIView()
required init(customParamArg: customType) {
self.customParam = customParamArg
super.init(frame: .zero)
// Setting up the view can be done here
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
// Can do the setup of the view, including adding subviews
setupConstraints()
}
func setupConstraints() {
// setup custom constraints as you wish
}
}
Here is how I do it on iOS 9 in Swift -
import UIKit
class CustomView : UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds);
//for debug validation
self.backgroundColor = UIColor.blueColor();
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
Here is a full project with example:
UIView Example Project (with SubView example)
Here is how I do a Subview on iOS in Swift -
class CustomSubview : UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds);
let windowHeight : CGFloat = 150;
let windowWidth : CGFloat = 360;
self.backgroundColor = UIColor.whiteColor();
self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);
//for debug validation
self.backgroundColor = UIColor.grayColor();
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}