vankuik.nl

Latest weblog entries

2017-11-07 Close button on popover

On iPhone, you usually add a cancel/close button to a modal popover. On iPad, there's usually no need to do so. Users just tap outside the popover to dismiss it. However when you're building an app for the iPad, and you support Split View and Multitasking, you suddenly do need it.

The problem is, how do you detect when to hide the close button, and when to show it. This is currently a bit of a hassle. My approach needs code in both the presenting and the presented viewcontroller:

    class PresentingViewController: UIViewController {    
        private var popoverViewController: PopoverViewController?
        
        override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
            self.popoverViewController?.doneButtonHidden = (self.traitCollection.horizontalSizeClass == .regular)
        }
        
        @objc func popoverAction() {
            let pvc = PopoverViewController()
            pvc.doneButtonHidden = (self.traitCollection.horizontalSizeClass == .regular)
            
            let navigationController = UINavigationController(rootViewController: pvc)
            navigationController.modalPresentationStyle = .popover
            navigationController.popoverPresentationController?.sourceView = self.view
            navigationController.popoverPresentationController?.permittedArrowDirections = .up
            navigationController.popoverPresentationController?.sourceRect = CGRect(x: 40, y: 40, width: 1, height: 0)
            self.present(navigationController, animated: true, completion: nil)
            self.popoverViewController = pvc
        }
    
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.yellow
    
            let barButtonItem = UIBarButtonItem(title: "Popover", style: .plain, target: self, action: #selector(popoverAction))
            self.navigationItem.leftBarButtonItem = barButtonItem
        }
    }
    class PopoverViewController: UIViewController {    
        var doneButtonHidden: Bool = false {
            didSet {
                let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissAction))
                self.navigationItem.leftBarButtonItem = self.doneButtonHidden ? nil : button
            }
        }
    
        @objc func dismissAction() {
            self.dismiss(animated: true, completion: nil)
        }
        
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.green
        }
    }

This works, but requires that you keep a reference to the PresentedViewController, to update the visibility of the close button. I don't much like having a class member variable when it could be a local variable, because it clutters up the code of the PresentingViewController. But this is the most concise and readable code I could come up with.

Note that when you use child ViewControllers, this does not seem to work. The reason is that traitCollectionDidChange() doesn't get called automatically. In that case, it could be acceptable to use viewDidLayoutSubviews().

2017-11-06 Xcode 9.1 unknown error

After upgrading to Xcode 9.1 (build 9B55), the following error would be shown in a modal dialog after opening an Xcode project:

    An unknown error occurred
    the path 'xxxxxx' exists but is not a tree (-3)

This particular path is included via a git submodule, but I'm not sure if that's related. The problem is fixed by removing the path its reference from Xcode, then add it again.

2017-10-09 using reduce in Swift

Here's a little playground that shows how to use reduce in Swift, specifically use it to report on a bunch of booleans. In the following code, we use reduce to determine whether all objects are enabled, or whether at least one is enabled.

    struct MyStruct {
        var enabled: Bool
        var text: String
    }
    let collection = [
        MyStruct(enabled: true, text: "one"),
        MyStruct(enabled: false, text: "two"),
        MyStruct(enabled: true, text: "three"),
    ]
    let enabledArray = collection.map { $0.enabled }
    let allEnabled = enabledArray.reduce(true) { $0 && $1 }
    print(allEnabled)
    let oneEnabled = enabledArray.reduce(false) { $0 || $1 }
    print(oneEnabled)

I've used this when I had a bunch of UITextField instances. Some of them were enabled, some not (i.e. property isEnabled set to true or false). Or for example when you have a bunch of UIView instances; are none of them hidden? That sort of stuff.

I could've posted this example with just an array of booleans as input, but I wanted to demonstrate the map as well. Often you don't just have a bare array of booleans.

One minor thing with the allEnabled variable in the example is that its result is meaningless when applied to an empty array. It'll return true. But what does that mean, right? You'll have to decide for yourself.

2017-10-06 reset display under macOS

Sometimes, you need to reset the display under macOS, without it being visible. Under Linux, this is incredibly easy; CTRL-ALT-F1 usually gives you a text console. You can then jump back to the GUI with alt-Left. Under macOS, this is slightly harder.

This does require preparation. First, you have to make a shortcut key to System Preferences:

  • Open System Preferences
  • Open the Keyboard applet
  • Open the Shortcuts tab
  • On the left, select App Shortcuts
  • On the right, below All Applications, add a shortcut key. The text should be exactly "System Preferences..." The shortcut key could be something like Ctrl-Cmd-,

In the future, it'll help you as follows: assuming you're logged in and for some reason don't have a display, do the following procedure:

  • Type Ctrl-Cmd-,
  • Hit Cmd-L to assure System Preferences is at the Show All view
  • Type "Displays" and hit space. This opens the Display Preferences.
  • Assuming the resolution is set to "Default for display", hit the Tab key twice and press Down arrow. This selects "scaled".
  • Hit tab three times. This selects the medium resolution. Hit space. This should reset the resolution and bring back your display.

More...

Weblog Archive

Weblog entries 2017

Weblog entries 2016

Weblog entries 2015

Weblog entries 2014

Weblog entries 2013

Weblog entries 2012

Weblog entries 2011

Weblog entries 2010

Weblog entries 2009

Weblog entries 2008

Weblog entries 2007

Weblog entries 2006

Weblog entries 2005

Weblog entries 2004

All weblog entries

Articles

Articles, chronologically (latest first). This is pretty old stuff.

Scribblings

Not yet finished. Maybe will never be finished. Maybe they'll get deleted. Who knows?

Programming:

System administration:

Others:

Files: