wicharek / UINavigationBar-FixedHeightWhenStatusBarHidden

Category for iOS 7 navigation bar, allowing it to keep its height even if status bar was hidden
https://www.factorialcomplexity.com/blog/fixed-height-navigation-bar-on-ios-7/
MIT License
57 stars 5 forks source link

Swift version #1

Open Isuru-Nanayakkara opened 10 years ago

Isuru-Nanayakkara commented 10 years ago

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 commented 10 years ago

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 commented 10 years ago

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:"))
    }

}
method_exchangeImplementations(
            class_getInstanceMethod(UINavigationBar.classForCoder(), Selector.convertFromStringLiteral("sizeThatFits:")),
            class_getInstanceMethod(UINavigationBar.classForCoder(), Selector.convertFromStringLiteral("sizeThatFits_FixedHeightWhenStatusBarHidden:"))

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 commented 7 years ago

Any updates on this?

BillCarsonFr commented 7 years ago

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)