Create instance of UIView subclass programmatically - ios

I've create a subclass of UIView which just has drawRect overridden. For simplicity's sake I am not creating a UIView in Interface Builder to represent what will be my subclass, but am rather wanting to just create an instance of it in my UIViewController code.
I've tried the following:
canvasView = CanvasView(frame: view.bounds)
canvasView.dataSource = self
As well as:
canvasView = UIView(frame: view.bounds) as! CanvasView
canvasView.dataSource = self
But for both of them I receive the error..
Value of type 'UIView' has no member 'dataSource'
What is the proper way of going about this? Do I need to create a UIView in IB? It is embedded within a UIScrollView, which I am also creating programmatically and is working well, so I would really prefer to not have to, if possible.

What you are doing is fine except for the fact that a UIView does not have a datasource.
More information about what you want to achieve would be helpful here.
Since I don't have a definition of your CanvasView it's hard to know what is going wrong. Below is something I put together in a playground that shows you one way you can set up a dataSource for your class.
import UIKit
protocol DataSourceProtocol {
var data: Any { get set }
}
class DataSource: DataSourceProtocol {
var data: Any
init(data: Any) {
self.data = data
}
}
class ViewWithDataSource: UIView {
var dataSource: DataSourceProtocol?
}
let dataSource = DataSource(data: "Some Data")
let myView = ViewWithDataSource(frame: CGRectZero)
myView.dataSource = dataSource
Edit: Edited out some incorrect statements/assumptions

Related

Update subviews of UIView from ViewController

I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.

How to add views while using a function in a separate class in Swift?

I created a new Swift-file with a class which holds a function which should add some UILabels, UITextFields and an UIButton to the viewcontroller.
When this code is in the ViewController it works, but in the separate file I get some error messages:
class InitialiseLabels {
func setupLabels() {
// multiple variables are declared without problems, but then:
let viewWidth = view.frame.width // Use of unresolved identifier 'view'
let viewHeight = view.frame.height // Use of unresolved identifier 'view'
view.addSubview(myLabel) // Use of unresolved identifier 'view'
// etcetera
myTextField.delegate = self as" UITextFieldDelegate // Use of unresolved identifier 'self'
view.addSubview(myTextField) // Use of unresolved identifier 'view'
}
}
I guess that I should add something in front of view to specify where it should go (like self.view but that one obviously doesn't work, the name of this ViewController also doesn't work). Can somebody please help me?
The short answer to your question "How to add views while using a function in a separate class in Swift?" is "Don't do that."
You should treat a view controller's views as private. The view controller should be responsible for them.
If you really wanted to let an outside object add views to your view controller (again, you shouldn't do that) you'd need a reference to the other view controller. It would be cleaner to create a function that would take the new views as parameters and add them for the caller:
public func addViewdToContent(_ views: [UIView])
Then from the other object, you'd need a reference to that view controller:
let otherViewConroller = //code to somehow get the other view controller.
let contentView = otherViewConroller.view
let viewWidth = contentView.frame.width
let viewHeight = view.frame.height
otherViewConroller.addViewToContent([myLabel]
Your next line doesn't really make sense, and neither does the error you're getting. It is a very bad idea to make some other object the delegate of a view controller's views. It is also a bad idea to inject views like text fields that need delegates into another view controller.
myTextField.delegate = self as" UITextFieldDelegate // makes no sense
Change
class InitialiseLabels
To
extension ViewController
Even if this is in another file, it has to be part of the original ViewController class, not some other class.
Create a class MyView and assign that to VC's Main View. And put your code inside that class.
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
// add code to align view properly
}
private setup() {
....
let viewWidth = frame.width // Use of unresolved identifier 'view'
let viewHeight = frame.height // Use of unresolved identifier 'view'
addSubview(myLabel)
myTextField.delegate = self as" UITextFieldDelegate // Use of unresolved identifier 'self'
addSubview(myTextField) /
...
}
}
Maybe create a singleton of the swift file and then refer to the singleton while calling the functions that add the UILabel, UIButton etc. to your current view controller
Suppose following is the swift file
class ButtonCreator : NSObject {
//Declare the Singleton
static let shared = buttonCreator()
func thisFunctionCreatesandReturnAButton(withFrame: CGRect) -> UIButton {
let button = UIButton(frame: withFrame)
return button
}
}
Now you can refer it throughout your application (any view controller) as
self.view.addSubview(ButtonCreator.shared.thisFunctionCreatesandReturnAButton(withFrame:CGRect(0,0,100,44)))
I found out what I was doing wrong: I put the declaration of the function in the viewdidload and because of that it was not available in the other parts of the view controller. Because of that I wanted to put it in a separate class or extension but I realised now that the solution is to put the function declaration right after the view controller class declaration.

Cannot assign value of type ViewSecond to ViewFirst

I try to assign UIView super class object's to other same type class object, getting error my code is below:
class ViewTest: UIViewController {
#IBOutlet var txtName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
var first = ViewFirst()
var second = ViewSecond()
func c() {
first = second
let datePicker = UIDatePicker()
txtName.inputView = datePicker
}
}
}
class ViewFirst: UIView {
}
class ViewSecond: UIView {
}
Why needed to type cast but txtName.inputView = datePicker working without any type cast
Thanks
This is wrong syntax. ViewSecond is not subclass or superclass to ViewFirst,even if you force cast the object like this:
first = second as! ViewFirst
you still get crash error.
So if ViewSecond is subclass to ViewFirst, just do force cast:
first = second as! ViewFirst
there will be well.
How cound I reply directlly? I am new...
Why txtName.inputView = datePicker without any cast type?
Because UIDatePicker is subClass of UIView! And then, the UIView is just a type define. So you have no need to do cast. For example:
func giveMeSomeView(view: UIView)
You can put it like UIButton, UIImageView.... But in the fuction, you have no idea which the exactlly view-Type, right? You just addSubview. Of course if you want to set something, you can have a implicit cast:
func giveMeSomeView(view: UIView) {
if let imageView = view as? UIImageView {
imageView.image = UIImage(string:"XXXX")
}
}
If there some word you could not understand, forgive it. My English is bad.
You should be more precise, do you have an error? Or which line doesn't work?
I guess the error is on this line :
first = second
You have to cast the object :
first = second as! ViewFirst
Please be more specific.
You can solve this problem bij making ViewSecond a subclass of ViewFirst instead of UIView.
For example:
class ViewFirst: UIView {
}
class ViewSecond: ViewFirst {
}

Strong references and UIView memory issues

I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}

When using Computed variable and Snapkit: No common superview between views

So here's the thing, I'm declaring a property like this:
var aNameLabel: UILabel {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "获取姓名失败", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}
And when I try to add constraint to the aNameLabel after I did someView.addSubView(aNameLabel), the app would crash every time at this constraint-adding thing, and says No common superview between views
However, when I change the variable into a let constant like this:
let aNameLabel = UILabel(text: "Allen", color: .whiteColor())
The constraint will be added with no complaint. Somebody can help me with this?
UPDATE
With the help of #par , I've changed my code into this:
var aNameLabel: UILabel = {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "BAD", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}()
And then the aNameLabel would always be assigned with value "BAD", while actually my guard let is successful. How do I fix this?
The problem is that you are creating a new UILabel every time you access the aNameLabel variable (a computed property function runs every time you access it). Presumably you are doing the same thing for the superview of this view (when you access someView in someView.addSubview() in your example above). If so, that's why there's no common superview and you are crashing.
You should create only one instance of each UIView used by your view controller, so creating a variable as a constant as you've shown is a great approach, or you can use a closure-initializer pattern like so:
var aNameLabel: UILabel = {
return UILabel(...)
}()
Notice in the above example the parentheses after the closing brace. Because it's a closure-initializer it will only be called once just like a let constant.
Often a UIView created with let isn't appropriate because constant properties need to be initialized before init() returns, and if you're creating views in a view controller you won't have a chance to add views until loadView() is called. In this case, declare your UIViews as implicitly-unwrapped optionals. They will be set to nil by init() which meets the initialization requirement, then you can set them to actual views later when viewDidLoad() is called, for example:
class MyViewController: UIViewController {
var someSubview: UIView!
override func viewDidLoad() {
super.viewDidLoad()
someSubview = UIView()
view.addSubview(someSubview)
// now set constraints with SnapKit
}
}

Resources