vankuik.nl

Latest weblog entries

2018-01-11 Navigationbar below statusbar on iOS 10

I had the strangest issue today. When testing my current app on iOS 10, the navigation bar would sit below the status bar. In other words, the navigation bar would be 44 pixels high, instead of 64 pixels. On iOS 11, this issue wasn't present.

This happened because our app does not use storyboards, and we used the following AppDelegate.swift:

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            let mainViewController = ViewController()
            let navigationController = UINavigationController(rootViewController: mainViewController)
            self.window?.rootViewController = navigationController
            self.window?.makeKeyAndVisible()
            
            return true
        }
    }

The problem is, that the window property is initialized too early. Moving it to didFinishLaunching fixes the problem:

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            let mainViewController = ViewController()
            let navigationController = UINavigationController(rootViewController: mainViewController)
            self.window?.rootViewController = navigationController
            self.window?.makeKeyAndVisible()
            
            return true
        }
    }

2017-12-23 Showing SVG image without libraries

Recently I had the need to show a logo in SVG format, but the project required that we did not include 3rd party libraries. The following Swift playground shows how you can show an SVG-based image using WKWebView.

Somehow, you need to know the image size beforehand -- or at least you need to be able to set width and height constraints. Lots of logos are square, thus there's no need to do anything special.

    import UIKit
    import PlaygroundSupport
    import WebKit
    class MyViewController : UIViewController {
        override func loadView() {
            let view = UIView()
            view.backgroundColor = .white
            let webView = WKWebView()
            webView.translatesAutoresizingMaskIntoConstraints = false
            let header =
    """
    <!DOCTYPE html><html style=\"overflow: hidden\">
    <head>
    <meta name="viewport" content="initial-scale=1.0" />
    <title>icon_people_search</title>
    </head>
    <body style=\"margin: 0;\">
    """
            let footer =
    """
    </body>
    """
            let svg =
    """
    <?xml version="1.0" encoding="iso-8859-1"?>
    <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
    <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
         viewBox="0 0 495 495" style="enable-background:new 0 0 495 495;" xml:space="preserve">
    <g>
        <polygon style="fill:#1A6FB0;" points="247.5,0 247.5,40 455,40 455,455 247.5,455 247.5,495 495,495 495,0     "/>
        <polygon style="fill:#1E81CE;" points="40,455 40,40 247.5,40 247.5,0 0,0 0,495 247.5,495 247.5,455     "/>
        <path style="fill:#1E81CE;" d="M205.767,405h65.266V247.413h43.798c0,0,4.104-25.428,6.103-53.235h-49.647v-36.264
            c0-5.416,7.109-12.696,14.153-12.696h35.564V90h-48.366c-68.478,0-66.872,53.082-66.872,61.009v43.356h-31.771v53.029h31.771V405z"
            />
    </g>
    </svg>
    """
            
            webView.loadHTMLString(header + svg + footer, baseURL: Bundle.main.bundleURL)
            view.addSubview(webView)
            
            let constraints = [
                webView.widthAnchor.constraint(equalToConstant: 100),
                webView.heightAnchor.constraint(equalToConstant: 100),
                webView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                webView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ]
            view.addConstraints(constraints)
            
            self.view = view
        }
    }
    // Present the view controller in the Live View window
    PlaygroundPage.current.liveView = MyViewController()

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.

UIAdaptivePresentationControllerDelegate

The following viewcontroller will display a popover, and if necessary a close button will be added.

    class PresentingViewController: UIViewController, UIPopoverPresentationControllerDelegate {
        
        // MARK: - UIPopoverPresentationControllerDelegate
        
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .fullScreen
        }
        
        func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
            guard let navigationController = controller.presentedViewController as? UINavigationController else {
                fatalError("Unexpected type of controller")
            }
            
            let closeButton = UIBarButtonItem(title: "Close", style: .done, target: self, action: #selector(close))
            navigationController.topViewController?.navigationItem.leftBarButtonItem = closeButton
            return navigationController
        }
        
        @objc func close() {
            self.dismiss(animated: true, completion: nil)
        }
        
        // MARK: - View cycle
        
        @objc func popoverAction() {
            let pvc = PopoverViewController()
            let navigationController = UINavigationController(rootViewController: pvc)
            navigationController.modalPresentationStyle = .popover
            navigationController.popoverPresentationController?.delegate = self
            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)
        }
        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 {    
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.green
        }
    }

My old solution

For historic accuracy, below you can find the manual solution. It's mainly obsolete.

    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.

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: