Hello I have a UIViewController which has 4 custom UIView. Deinit function inside UIViewController is called when I change the rootViewController but doesn't called inside custom UIView. Therefore, It persists in memory after I change root controller. If I call myCustomView.removeFromSuperView() inside ViewControllers Deinit function, It works. However, I don't wanna write it everytime in a viewcontrollers so I want to write it inside custom UIView class but Deinit inside Custom UIView never get called.
How I declare custom view:
lazy var myCustomView = WelcomeScreenButtonView(text: getLabelText(key: SUPPORTEDSERVICES), imageName: "img")
let firstStackView = UIStackView()
override func viewDidLoad(){
//I put it inside a UIStackView
firstStackView.addArrangedSubview(myCustomView)
view.addSubView(firstStackView)
}
deinit{
//If I do it like this, It works but I don't wanna call it in here
myCustomView.removeFromSuperView()
}
My Custom UIView Class:
class WelcomeScreenButtonView: UIView {
lazy var label = UILabel()
private lazy var logoImage = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
//This one never get called
print("WelcomeScreen Button Deinited")
removeFromSuperview()
}
}
EDIT: If I change my custom view to UIView() (all other codes are same). It is deallocating. Therefore, I guess I have something to do with custom UIView class.
Related
I have a UIView which I am passing data from another view using getData(data: MydataModel). The data arrives at the second view but with a delay.
Here my sample code:
class MyView: UIView {
var myData: MydataModel?
init() {
loadView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func getData(data: MydataModel) {
self.myData = data
}
func loadView() {
label.text = myData?.city
}
}
I tried using property observer but I run into issues and not updating content inside the view.
The problem is that calling getData(data:) only sets the myData variable - it doesn't update any visible content in the view.
You can add a property observer to myData that calls loadView to update the view:
var myData: MydataModel? {
didSet {
loadView()
}
}
If you're still running into issues, make sure that all view updates are happening on the main thread, so perhaps you need call getData(data:) within DispatchQueue.main.async.
Instead of letting any UIView be inserted into a UIStackView, can I set it so that only views which conform to a custom protocol, say 'MyProtocol' can be inserted?
You could subclass uiview and make it accept the view and the protocol (from swift 4+, see https://stackoverflow.com/a/45276465/8517882)
It would look something like this:
protocol SomeProtocol {
func someFunc()
}
class CustomStack: UIView {
private let stack = UIStackView()
init() {
super.init(frame: CGRect.zero)
self.addSubview(stack)
// then you can constraint the stack to self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addSubview(_ view: UIView & SomeProtocol) {
self.stack.addSubview(view)
view.someFunc() // you can call the protocol methods on the view
}
func addArrangedSubviews(_ view: UIView & SomeProtocol) {
stack.addArrangedSubview(view)
view.someFunc() // you can call the protocol methods on the view
}
}
I work with Nibs. I have two screens that will use the "same" UIView component with the same behavior. It's not the same component because in each screen i placed a UIView and made the same configuration, as show on the image.
To solve this and prevent replicate the same code in other classes i wrote one class, that is a UIView subclass, with all the functions that i need.
After that i made my custom class as superclass of these UIView components to inherit the IBOutlets and all the functions.
My custom class is not defined in a Nib, is only a .swift class.
I made all the necessary connections but at run time the IBOutlets is Nil.
The code of my custom class:
class FeelingGuideView: UIView {
#IBOutlet weak var firstScreen: UILabel!
#IBOutlet weak var secondScreen: UILabel!
#IBOutlet weak var thirdScreen: UILabel!
#IBOutlet weak var fourthScreen: UILabel!
private var labelsToManage: [UILabel] = []
private var willShow: Int!
private var didShow: Int!
override init(frame: CGRect) {
super.init(frame: frame)
self.initLabelManagement()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
private func initLabelManagement() {
self.initLabelVector()
self.willShow = 0
self.didShow = 0
self.setupLabelToShow(label: labelsToManage[0])
self.setupLabels()
}
private func initLabelVector() {
self.labelsToManage.append(self.firstScreen)
self.labelsToManage.append(self.secondScreen)
self.labelsToManage.append(self.thirdScreen)
self.labelsToManage.append(self.fourthScreen)
}
private func setupLabels() {
for label in labelsToManage {
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor(hex: "1A8BFB").cgColor
}
}
func willShowFeelCell(at index: Int) {
self.willShow = index
if willShow > didShow {
self.setupLabelToShow(label: labelsToManage[willShow])
}
else if willShow < didShow {
for i in didShow ... willShow + 1 {
let label = labelsToManage[i]
self.setupLabelToHide(label: label)
}
}
}
private func setupLabelToShow(label: UILabel) {
label.textColor = UIColor.white
label.backgroundColor = UIColor(hex: "1A8BFB")
}
private func setupLabelToHide(label: UILabel) {
label.textColor = UIColor(hex: "1A8BFB")
label.backgroundColor = UIColor.white
}
}
I found this question similar to mine: Custom UIView from nib inside another UIViewController's nib - IBOutlets are nil
But my UIView is not in a nib.
EDIT:
I overrided the awakeFromNib but it neither enter the method.
More explanation:
My custom class is only superClass of this component:
Which i replicate on two screens.
One screen is a UITableViewCell and the another a UIViewController.
It's all about to manage the behavior of the labels depending on the screen that is showing at the moment on the UICollectionView
When the initLabelVector() function is called at the required init?(coder aDecoder:) it arrises a unwrap error:
The error when try to open the View:
Cannot show the error with the UITableViewCell because it is called at the beginning of the app and don't appear nothing. To show the error with the screen i needed to remove the call of the UITableViewCell.
The UITableViewCell is registered first with the tableView.register(nib:) and after using the tableView.dequeueReusebleCell.
The UIViewController is called from a menu class that way:
startNavigation = UINavigationController(rootViewController: FellingScreenViewController())
appDelegate.centerContainer?.setCenterView(startNavigation, withCloseAnimation: true, completion: nil)
The problem is this code:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
The trouble is that init(coder:) is too soon to speak of the view's outlets, which is what initLabelManagement does; the outlets are not hooked up yet. Put this instead:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.initLabelManagement()
}
How I arrived at this answer:
As a test, I tried this:
class MyView : UIView {
#IBOutlet var mySubview : UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
print(#function, self.mySubview)
}
override func awakeFromNib() {
super.awakeFromNib()
print(#function, self.mySubview)
}
}
Here's the output:
init(coder:) nil
awakeFromNib() <UIView: 0x7fc17ef05b70 ... >
What this proves:
init(coder:) is too soon; the outlet is not hooked up yet
awakeFromNib is not too soon; the outlet is hooked up
awakeFromNib is called, despite your claim to the contrary
When the init() from my custom class is called the IBOutlets are not hooked up yet.
So, I created a reference on the parent view and called the iniLabelManagement() from the viewDidLoad() method and everything worked.
Thank you matt, for the help and patience!
I created a nib file with a custom collectionViewCell and attached to a viewController
class CustomCollectionView: UICollectionViewCell{}
Now I have to use the exact cell inside a tableView. I created a new nib file and viewController
class CustomTableView: UITableViewCell{}
and I copied the hole code of CustomCollectionView on it. every thing is working fine but I believe that it dose not make sense to copy the hole exact code of CustomCollectionView into CustomTableView and to use the exact same nib file but with a tableViewCell instead of collectionViewCell on it. Is there any way to optimize what I did?
As you said in a comment in suhit's answer, you can do this by using a common view in both the CollectionViewCell and TableViewCell subclasses. You don't need a ViewController since it adds extra overhead. A simple UIView is enough. Some code to show what I mean:
class CustomTableViewCell: UITableViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
class CustomCollectionViewCell: UICollectionViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
extension UIView {
func fillSuperview() {
guard let superview = superview else {
return print("no superview")
}
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
rightAnchor.constraint(equalTo: contentVisuperviewew.rightAnchor).isActive = true
}
}
A sample implementation for the CustomView class:
class CustomView: UIView {
func initialize() {
//...
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
}
If you wish to create your custom view in a xib that's also fine, but it's a little trickier. This is beyond the scope of the question but I'm just going to leave a link here in case you need it.
If you want to use same view then its better to use similar type view i.e. use collectionView at both places so that you can use the CustomCollectionViewCell in both ViewControllers. UICollectionView is highly customisable so you can do whatever you want to do with UITableView in UICollectionView as well.
I use code to create the view (with subviews) for UIViewController's this is how I do it:
override loadView()
class MYViewController: UIViewController {
var myView: MyView! { return self.view as MyView }
override func loadView() {
view = MyView()
}
}
and here is how I create my custom view:
class MyView: UIView {
// MARK: Initialization
override init (frame : CGRect) {
super.init(frame : frame)
addSubviews()
setupLayout()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
// MARK: Build View hierarchy
func addSubviews(){
// add subviews
}
func setupLayout(){
// Autolayout
}
// lazy load views
}
I do this for all my View Controllers and I am looking for more elegant way, because this process is repetitive, so is there any solution for make that generic for example, create a super abstract class, or create an extension for UIViewController and UIView, Protocols ? I am new for swift and I think that Swift can have a better elegant solution with it's modern patterns
If you are wanting to create many different controllers with custom view classes my recommended solution would be along these lines:
First implement a custom view subclass the way you want to be able to use it, here I have used the one you had in your question. You can then subclass this anywhere you need it and just override the relevant methods.
class CustomView: UIView {
// MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
addSubviews()
setupLayout()
}
required init() {
super.init(frame: .zero)
addSubviews()
setupLayout()
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
// MARK: Build View hierarchy
func addSubviews(){
// add subviews
}
func setupLayout(){
// Autolayout
}
}
Then create a generic custom view controller that allows specification of a class as a generic parameter so that you can easily create a controller with a custom view class.
class CustomViewController<T: CustomView>: UIViewController {
var customView: T! { return view as! T }
override func loadView() {
view = T()
}
init() {
super.init(nibName: nil, bundle: nil)
}
}
Then if you wanted to define a new custom view and create a controller that uses it you can simply:
class AnotherCustomView: CustomView { /* Override methods */ }
...
let controller = CustomViewController<AnotherCustomView>()
Boom!
If you wanted you could even typealias this new controller type to make it even more elegant:
class AnotherCustomView: CustomView { /* Override methods */ }
...
typealias AnotherCustomViewController = CustomViewController<AnotherCustomView>
let controller = AnotherCustomViewController()