0

I have a child-view controller that do not want to have system safe areas or viewSafeAreaInsetsDidChange() called on rotation. So far this is not working:

In Superview:

let childVC = UIViewController()
self.addChild(childVC)
childVC.view.frame = CGRect(x:0, y: self.view.bounds.height - 300, width: self.view.bounds.width, height: 300)
self.view.addSubview(childVC.view)
childVC.didMove(toParent: self)

In Child View Controller:

class childVC: UIViewController {
    let picker = UIPickerView()
    
    override final func loadView() {
        super.loadView()
    
        self.viewRespectsSystemMinimumLayoutMargins = false
        self.view.insetsLayoutMarginsFromSafeArea   = false
        self.view.preservesSuperviewLayoutMargins.  = false
    }

    override final func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //Some mention this to be inside viewDidAppear
        self.viewRespectsSystemMinimumLayoutMargins = false
        self.view.insetsLayoutMarginsFromSafeArea = false
        self.view.preservesSuperviewLayoutMargins = false

        //Add picker view here
        self.picker.delegate = self
        self.picker.dataSource = self
        self.picker.frame = self.view.bounds
        self.view.addSubview(self.picker)
    }

    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        //This gets called everytime the device rotates, causing the picker view to redraw and reload all components. Trying to avoid this method being called.

        print ("viewSafeAreaInsetsDidChange")
        print (self.view.safeAreaInsets)
    }
}

Issue

Everytime the device rotates, viewSafeAreaInsetsDidChange() gets called causing the picker view to redraw and reload.

Goal

Trying to avoid viewSafeAreaInsetsDidChange() being called every time the device rotates.

Gizmodo
  • 3,151
  • 7
  • 45
  • 92
  • 1
    I would be surprised that `viewSafeAreaInsetsDidChange()` is what's actually triggering the picker view reload. Can you post enough code to **show** that's the cause of the reload? – DonMag Jul 24 '23 at 19:09
  • 1
    For example, this code: https://pastebin.com/vLkNj6vy. ... adds a picker view at a specific rect - no resizing on rotation - and the `.dataSource` / `.delegate` funcs are only called on initial load and when scrolling the picker. They are **not** called on device rotation. – DonMag Jul 24 '23 at 19:48
  • `viewSafeAreaInsetsDidChange()` doesn't get called in an iPhone SE and the picker does not get reloaded. – Gizmodo Jul 24 '23 at 21:49
  • If we focus on `viewSafeAreaInsetsDidChange()` to not to get called, what am I doing wrong here? – Gizmodo Jul 24 '23 at 21:50
  • 1
    Tough to say what's going on just looking at your snippets... can you provide testable code? – DonMag Jul 24 '23 at 22:05
  • Unfortunately, I cannot post more. I would like to get to the bottom of why safe areas are still a factor... – Gizmodo Jul 25 '23 at 01:57
  • @Gizmodo don't call the super function here: override func viewSafeAreaInsetsDidChange() { print ("viewSafeAreaInsetsDidChange") print (self.view.safeAreaInsets) } – linh lê Jul 25 '23 at 02:59
  • 1
    @Gizmodo - you could at least post enough code to show what you are *trying* to do. Just using the `addChild` code you included, the picker view disappears on device rotation - which, I'm guessing, is not your goal. As I showed in the sample code I linked to, `viewSafeAreaInsetsDidChange()` ***DOES*** get called, but ***DOES NOT*** cause the picker view to reload its data. Also, in **this** code (loading picker in a child controller): https://pastebin.com/cZPTZ17C ... again, `viewSafeAreaInsetsDidChange()` ***DOES*** get called, but ***DOES NOT*** cause the picker view to reload. – DonMag Jul 25 '23 at 11:22
  • Yes, let me get back with more code. – Gizmodo Jul 25 '23 at 12:27
  • I had the view move around with frame changes and noticed whenever`viewSafeAreaInsetsDidChange()` gets called, UIPickerView gets reloaded. So I need to figure out a way to stop `viewSafeAreaInsetsDidChange()` from being called. – Gizmodo Jul 25 '23 at 23:46
  • @Gizmodo - I *really, **really, REALLY*** believe you are chasing your tail. `viewSafeAreaInsetsDidChange()` is a *notification* sent to the controller. It is not what is ***causing*** the picker view reload. – DonMag Jul 26 '23 at 13:36

1 Answers1

2

viewSafeAreaInsetsDidChange()

Called to notify the view controller that the safe area insets of its root view changed.

This is a notification call. You ignore it, or respond to it... but you cannot prevent the safe area insets from changing.

These two examples: https://pastebin.com/vLkNj6vy and https://pastebin.com/cZPTZ17C show that viewSafeAreaInsetsDidChange() is called on device rotation, but the picker view does NOT reload.

The picker view will reload if its frame is outside the affected safe-area change.

Here is a quick example:

class MovingPickerVC: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    
    let picker = UIPickerView()
    
    var vConstraints: [NSLayoutConstraint] = []
    var hConstraints: [NSLayoutConstraint] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        picker.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(picker)

        // keep picker INSIDE the safe-area
        //  this WILL NOT cause reload on device rotation
        let g = view.safeAreaLayoutGuide
        
        // extend picker frame OUTSIDE the safe-area
        //  this WILL cause reload on device rotation
        //let g = self.view!
        
        NSLayoutConstraint.activate([
            picker.widthAnchor.constraint(equalToConstant: 300.0),
            picker.heightAnchor.constraint(equalToConstant: 160.0),
        ])

        vConstraints = [
            picker.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            picker.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ]
        hConstraints = [
            picker.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            picker.trailingAnchor.constraint(equalTo: g.trailingAnchor),
        ]

        // so we can see its frame
        picker.backgroundColor = .yellow
        
        self.picker.delegate = self
        self.picker.dataSource = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if self.traitCollection.verticalSizeClass == .regular {
            NSLayoutConstraint.activate(vConstraints)
        } else {
            NSLayoutConstraint.activate(hConstraints)
        }
    }
    
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        
        coordinator.animate(alongsideTransition: { [unowned self] _ in
            if newCollection.verticalSizeClass == .regular {
                NSLayoutConstraint.deactivate(self.hConstraints)
                NSLayoutConstraint.activate(self.vConstraints)
            } else {
                NSLayoutConstraint.deactivate(self.vConstraints)
                NSLayoutConstraint.activate(self.hConstraints)
            }
        }) { [unowned self] _ in
            // if we want to do something on completion
        }
        
    }
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        //This gets called everytime the device rotates, causing the picker view to redraw and reload all components. Trying to avoid this method being called.
        
        print ("viewSafeAreaInsetsDidChange")
        print (self.view.safeAreaInsets)
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        print("num components: 1")
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        print("num rows: 30")
        return 30
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        print("Title for Row:", row)
        return "Row: \(row)"
    }
    
}

Looks like this:

enter image description here

and rotating the device:

enter image description here

When that is run, we will see the viewSafeAreaInsetsDidChange() being logged, but the picker view will NOT reload.

However, in viewDidLoad(), if we change the constraints to the view instead of the safe area:

    // keep picker INSIDE the safe-area
    //  this WILL NOT cause reload on device rotation
    //let g = view.safeAreaLayoutGuide
    
    // extend picker frame OUTSIDE the safe-area
    //  this WILL cause reload on device rotation
    let g = self.view!

enter image description here

enter image description here

we've placed the picker view frame outside the safe area, and it will reload on rotation.

If you don't want the picker to reload (for some reason), you'll need to manage that via your dataSource / delegate funcs.

DonMag
  • 69,424
  • 5
  • 50
  • 86