Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift version #1

Open
Isuru-Nanayakkara opened this issue Sep 3, 2014 · 4 comments
Open

Swift version #1

Isuru-Nanayakkara opened this issue Sep 3, 2014 · 4 comments

Comments

@Isuru-Nanayakkara
Copy link

Hi! I'm currently facing this problem and while I was searching for a solution, I came across your category and it seems like just what I've been looking for.

Although the problem is I'm using Swift. Do you think you can convert it in to Swift?

@wicharek
Copy link
Owner

wicharek commented Sep 3, 2014

Hi, you can try using it in your project as is. It is possible to use Swift and Objective C together:

https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_76

Although, it might not work, because I am using "method swizzling" technique. Not sure if it will work in Swift project. However, it is worth trying since it shouldn't be hard.

@Isuru-Nanayakkara
Copy link
Author

I was aware of using Objective-C code in Swift via bridging headers and stuff but I wanted to go for a more pure Swift way :)

I did a little bit of research and found that method swizzling is indeed possible in Swift as long as either the class is a descendant of NSObject or using the @objc directive. Since UINavigationBar is indirectly descended from NSObject, it was possible to swizzling. But this came with a few tradeoffs.

Before explaining those, please have a look at the end result. The Swift equivalent to Objective-C categories is extensions so I implemented this as an extension.

import Foundation
import UIKit

let FixedNavigationBarSize = "FixedNavigationBarSize";

extension UINavigationBar {

    var fixedHeightWhenStatusBarHidden: Bool {
        get {
//            return objc_getAssociatedObject(self, FixedNavigationBarSize).boolValue
            return true
        }
        set(newValue) {
            objc_setAssociatedObject(self, FixedNavigationBarSize, NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize {

        if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden {
            let newSize = CGSizeMake(self.frame.size.width, 64)
            return newSize
        } else {
            return sizeThatFits_FixedHeightWhenStatusBarHidden(size)
        }

    }

    /**
    This function isn't getting executed.
    */
    override public class func load() {
//        method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
    }

}
  • The biggest problem is the load() method does not fire in Swift. No idea why. So swizzling code inside the method never gets fired either. So I moved it to app delegate's didFinishLaunchingWithOptions method.
method_exchangeImplementations(
            class_getInstanceMethod(UINavigationBar.classForCoder(), Selector.convertFromStringLiteral("sizeThatFits:")),
            class_getInstanceMethod(UINavigationBar.classForCoder(), Selector.convertFromStringLiteral("sizeThatFits_FixedHeightWhenStatusBarHidden:"))
  • Second issue comes as a result of the first problem. Since swizzling happens in the app delegate, setting fixedHeightWhenStatusBarHidden to true in a view controller causes the app to crash at the property's getter with the error unexpectedly found nil while unwrapping an Optional value. So I had to hardcode 'true' in the getter in order to get this to work.

Now finally the extension works. But the price of all these workarounds is reusability. Since there are hardcoded values and some parts of it spread across the app files, it's not a good, compact solution in Swift.

Unless there is a way to get the swizzling code back into the extension itself.

Anyway I uploaded a test Xcode project here if you wanna take a look at it and give it a try. I got help from StackOverflow to resolve some snags I hit on the way.

@txaiwieser
Copy link

Any updates on this?

@BillCarsonFr
Copy link

BillCarsonFr commented Dec 26, 2016

Hi,

I know it's a bit old, but i just had this problem today, and maybe it can help someone.

It seems that know it can be solved without swizzling or extension.
Just create your own subclass of UINavigationBar with the size that fits that you want, then instantiate the NavigationController using the creator that allows to set a custom navbar class.

Looks like this:

class FixedHeightNavbar : UINavigationBar {

  open override func sizeThatFits(_ size: CGSize) -> CGSize {
        let origSize = super.sizeThatFits(size) // height can be 64 / 44 / 32 ...
        let kStatusBarHeight = UIApplication.shared.statusBarFrame.size.height
        let interfaceOrientation = UIApplication.shared.statusBarOrientation
        if interfaceOrientation == .portrait && kStatusBarHeight == 0 {
            //correct and add back the 20 pt
            return CGSize(width: origSize.width, height: origSize.height + 20)
        }
        return origSize
    }
}

...

And instantiate nav controller like this:

let nv = UINavigationController(navigationBarClass: FixedHeightNavbar.self, toolbarClass: nil)
                nv.setViewControllers([myRootVC], animated: true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants