Why is deinit not called until UIView is added to parent again? - swift

I have a UIView that am adding to a UIViewController and am generally testing de-initialization to make sure I am doing things right. But when I don't set the variable in my viewController to nil and only use .removeFromSuperView() , the deinit() method in UIView won't be called until I add the UIView another time then its called. But if I use removeFromSuperView() and set the variable to nil then deinit() is called right away. Why is that?
Here's UIView() class:
class TestView: UIView {
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
print("init is happneing")
}
deinit {
print("de init is happneing")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here's the parent ViewController :
class MyViewController: UIViewController {
var tstview : TestView?
//adding the UIView by tapping on a button
#IBAction func addView(_ sender: UIButton) {
let test = TestView()
tstview = test
tstview?.frame = CGRect(x: 50, y: 60, width: self.view.frame.width-100, height: self.view.frame.height-200)
tstview?.backgroundColor = UIColor.white
self.view.addSubview(tstview!)
}
override func viewDidLoad() {
super.viewDidLoad()
}
//removing UIView by touching elsewhere
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tstview?.removeFromSuperview()
// tstview = nil
}
}

deinit is called when no one is referencing the object. If you don't set tstview to nil, your MyViewController is still referencing it, thus deinit won't be called. When you call addView, the statement tstview = test finally removes the last reference to the old view, thus triggering the deinitializer.
You can read more about the concept of deinitialization in the Swift documentation.
If you want to be notified as soon as the view is detached, override willMove(toSuperview:) instead.
class TestView: UIView {
...
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
print("removed from parent")
}
}
}

Related

viewDidLoad() not called

Either there is something weird in my project (Mojave, XCode10) or I am missing something very basic.
This is my whole code:
import Foundation
import Cocoa
class ViewController: NSViewController {
public init () {
super.init(nibName: nil, bundle: nil)
self.view = ConfigView(rect: NSRect(x: 0, y: 0, width: 400, height: 300))
print("2")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
print("3")
}
override func viewDidLoad() {
super.viewDidLoad()
print("4")
}
override func viewWillAppear() {
super.viewWillAppear()
print("5")
}
override func viewDidAppear() {
super.viewDidAppear()
print("6")
print("Is loaded: \(isViewLoaded)")
}
}
class ConfigView: NSView {
public init(rect: NSRect) {
super.init(frame: rect)
print("1")
}
required public init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want to setup stuff in the viewDidLoad() method, but this method is never called. The console shows:
1
2
5
6
Is loaded: true
So, obviously loadView() and viewDidLoad() are not called, but afterwards when the view appeared isViewLoaded() shows true.
Why is that lifecycle method being ignored?
I suspect what is happening here is that since you are initializing the view property in the init function, when loadView() is called, it detects that the view has already been loaded and therefore does not call the viewDidLoad().
Have you tried putting the view instantiation into the loadView() function instead of the init? In any case, I would not recommend creating the view in the init function.
If you implement loadView, you must not call super. And you must make a view and assign it as self.view. Fix that and all will be well. Of course, in real life, you would never implement loadView, so all this is academic.

Subclass of NSDatePicker mouseDown event changes datePicker instance

I have instantiated and initialized a subclass of NSDatePicker in AppDelegate -> applicationDidFinishLaunching and overrode mouseDown(with event: NSEvent)in the datePicker subclass. When I press the mouse button in the datePicker and break in its overridden mouseDown func the instance is not the one I instantiated in applicationDidFinishLaunching and so is not initialized.
I've tried creating the instance at different entry points thinking it might be a timing thing but I've gotten nowhere. I'm out of ideas and feeling a little feeble. Any Help?
The datePicker:
import Cocoa
class AlarmIVDatePicker: NSDatePicker {
var viewController: ViewController!
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
}
override func mouseDown(with event: NSEvent) {
let stop = 0
}
}
The ViewController:
class ViewController: NSViewController, NSWindowDelegate{
var alarmIVDatePicker: AlarmIVDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
alarmIVDatePicker = AlarmIVDatePicker()
alarmIVDatePicker.viewController = self
}
I expected I could access the values I had set but the instance is not the one I created and all the values are nil
Okay. Here's what you can do to track this down. Add this code to your AlarmIVDatePicker class:
override init(frame frameRect: NSRect) {
Swift.print("AlarmIVDatePicker being called here")
super.init(frame: frameRect)
}
convenience init() {
self.init(frame: .zero)
}
required init?(coder: NSCoder) {
Swift.print("hey, you DID drop this into a storyboard or XIB file!")
super.init(coder: coder)
}
If you set breakpoints inside these init methods, you'll catch when and where they are being called where you did not expect.

How do I call multiple custom alert views on a UIButton tap from a single UIViewController?

I have a ViewControllerA and two custom alerts and I'm calling the first custom alert on the buttonclick action of the ViewControllerA.
In first custom alert I'm having a text field and a button.
When I click on the button action of first custom alert , I want to open the second custom alert and both alerts I want show on ViewControllerA.
The expected result is to show both the custom alerts on the single ViewControllerA
Please help me out to solve this problem
class ViewControllerA : UIViewController{
#IBAction func ViewControllerAButtonTapped(_ sender: Any) {
FirstCustomalert.instance.ShowAlert()
self.dismiss(animated: true, completion: nil)
}
}
class FirstCustomAlert : UIView{
#IBOutlet var parentView: UIView!
static let instance = FirstCustomAlert()
#IBOutlet weak var firstTextField: UITextField!
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("FirstCustomAlert", owner: self, options: nil)
CommonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func CommonInit(){
alertView.frame = CGRect(x: center.x, y: center.y, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
ParentView.autoresizingMask = [ .flexibleHeight , .flexibleWidth ]
}
func ShowAlert(){
UIApplication.shared.keyWindow?.addSubview(ParentView)
}
#IBAction func SubmitBittonTapped(_ sender: Any) {
SecondCustomAlert.instances.ShowAlerts()
}
}
class SecondCustomAlert : UIView{
#IBOutlet var parentView: UIView!
#IBOutlet weak var secondTextField: UITextField!
static let instances = SecondCustomAlert()
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("SecondCustomAlert", owner: self, options: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func CommonInit(){
parentView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
parentView.autoresizingMask = [.flexibleWidth , .flexibleHeight ]
}
func ShowAlerts(){
UIApplication.shared.keyWindow?.addSubview(parentView)
}
}
Firstly, there are a number of things you need to change:
Naming convention:
Your methods should be named with lowerCamelCase and only classes and objects should be CamelCase.
You are building a custom alert to be displayed from anywhere in the app. Since you are already using .xib, I would suggest that you refactor your alert into a ViewController and use present(viewController: animated:) with presentationStyle of .overCurrentContext. You can get the topViewController on the keyWindow which you have and call the present function. Then in your AlertViewController you can do separate animations to display the second view of the alert upon button click.
What you are trying to do now is put too much logic into a UIView, which is not best practise and it isn't very scalable. Plus it looks messy. You are trying to achieve something relatively simply but the current approach already creates 2 customs views and you are already encountering callback issues.
ADD:
class ViewControllerA : UIViewController{
#IBAction func ViewControllerAButtonTapped(_ sender: Any) {
let customAlert = CustomAlertViewController()
customAlert.modalPresentationStyle = .overCurrentContext
self.present(customAlert, animated: true, completion: nil)
}
}

Set CustomView delegate before init

Although I could create a func setImages () in my CustomView class and call this after initializing myCustomView, I would like to understand if there's a cleaner way to set a view's delegate so that it is accessible when being initialized.
My main ViewController contains
class Main: UIViewController, CustomViewDelegate {
var imagesArray:[UIImage] = [Image1,Image2,Image3,Image4,Image5]
var myCustomView = CustomView()
override func viewDidLoad() {
super.viewDidLoad()
myCustomView.delegate = self
myCustomView = CustomView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
//this causes init of CustomView, but delegate is now nil and button images don't load
}
}
My CustomView file contains
var buttonsArray = [Button1,Button2,Button3,Button4,Button5]
override init(frame: CGRect) {
super.init(frame : frame)
for n in 0..< buttonsArray.count {
buttonsArray[n].setImage(delegate?.imagesArray[n], for: .normal)
}
}
You can create a new initializer which takes a frame and a delegate type and set the delegate before you set images to buttons
init(frame: CGRect,sender: CustomViewDelegate) {
super.init(frame : frame)
self.delegate = sender
for n in 0..< buttonsArray.count {
buttonsArray[n].setImage(delegate?.imagesArray[n], for: .normal)
}
}
For that, you have to confirm your viewController for the delegate(obviously).Call your customView like this in viewController:
class ViewController: UIViewController, CustomViewDelegate {
var myCustomView: CustomView!
override func viewDidLoad() {
myCustomView = CustomView(frame: self.view.bounds, sender: self)
}
}
hope it helps!

Programmatically access members of a UIView subclass in a UIViewController subclass

I want to put all subview properties and my subview setup code in a UIView subclass and load that into my UIViewController subclass using loadView(). Then access the UIView subclass members without casting the view property of UIViewController all the time.
This is my UIView subclass AwesomeClass
class AwesomeView: UIView {
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
And my UIViewController subclass AwesomeViewController
class AwesomeViewController: UIViewController {
override func loadView() {
let view = AwesomeView()
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I could do something like:
var subclassedView:AwesomeView {
get {
return self.view as! AwesomeView
}
}
and
subclassedView.testView.backgroundColor = UIColor.blue
But is there a way to call testView directly with self.view in the AwesomeViewController?
Edit:
What I am looking for is Covariant return type in swift.
You can you something like this:
Instantiate an instance of AwesomeViewController in AwesomeView
class AwesomeView: UIView {
var exampleColorVariable:UIColor?
//here you instantiate your view controller
var awesomeViewController = AwesomeViewController()
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
then you can access any method in AwesomeView changing a little bit your code
class AwesomeViewController: UIViewController {
lazy var awesomeView: AwesomeView = {
let view = AwesomeView()
view.awesomeViewController = self
return view
}()
func setupView() {
view.addSubview(awesomeView)
// your constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
// NOW YOU CAN ACCESS ANY METHOD IN YOUR VIEW awesomeView.yourFunction()
// or you access that variable
awesomeView.exampleColorVariable = .red // you can now omit UIColor in swift3
}
}