There is a little problem with UINavigationBar
which I\'m trying to overcome. When you hide the status bar using prefersStatusBarHidden()
method in
Update for Swift 4.
initialize() is no longer exposed: Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift
So the way to do it now is to run your swizzle code via a public static method or via a singleton.
Method swizzling in swift 4
Objective-C, which uses dynamic dispatch supports the following:
Class method swizzling:
The kind you're using above. All instances of a class will have their method replaced with the new implementation. The new implementation can optionally wrap the old.
Isa-pointer swizzling:
An instance of a class is set to a new run-time generated sub-class. This instance's methods will be replaced with a new method, which can optionally wrap the existing method.
Message forwarding:
A class acts as a proxy to another class, by performing some work, before forwarding the message to another handler.
These are all variations on the powerful intercept pattern, which many of Cocoa's best features rely on.
Enter Swift:
Swift continues the tradition of ARC, in that the compiler will do powerful optimizations on your behalf. It will attempt to inline your methods or use static dispatch or vtable dispatch. While faster, these all prevent method interception (in the absence of a virtual machine). However you can indicate to Swift that you'd like dynamic binding (and therefore method interception) by complying with the following:
public dynamic func foobar() -> AnyObject
In the example you provide above, these requirements are being met. Your class is derived transitively from NSObject
via UIView
and UIResponder
, however there is something odd about that category:
Try instead to move the Swizzling code into your AppDelegate's did finish launching:
//Put this instead in AppDelegate
method_exchangeImplementations(
class_getInstanceMethod(UINavigationBar.self, "sizeThatFits:"),
class_getInstanceMethod(UINavigationBar.self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
While this isn't a direct answer to your Swift question (seems like it may be a bug that load
doesn't work for Swift extensions of NSObject
s), however, I do have a solution to your original problem
I figured out that subclassing UINavigationBar and providing a similar implementation to the swizzled solution works in iOS 7 and 8:
class MyNavigationBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
if UIApplication.sharedApplication().statusBarHidden {
return CGSize(width: frame.size.width, height: 64)
} else {
return super.sizeThatFits(size)
}
}
}
Then update the class of the Navigation Bar in your storyboard, or use init(navigationBarClass: AnyClass!, toolbarClass: AnyClass!)
when constructing your navigation controller to use the new class.
Swift doesn't implement the load() class method on extensions. Unlike ObjC, where the runtime calls +load for each class and category, in Swift the runtime only calls the load() class method defined on a class; Swift 1.1 ignores load() class methods defined in extensions. In ObjC, it is acceptable for each category to override +load so that each class/category can register for notifications or perform some other initialization (like swizzling) when that category/class loads.
Like ObjC, you should not override the initialize() class method in your extensions to perform swizzling. If you were to implement the initialize() class method in multiple extensions, only one implementation would be called. This is different than the +load method where the runtime calls all implementations when the app launches.
So, to initialize the state of an extension, you can either call the method from the app delegate when the app finishes launching, or from an ObjC stub when the runtime calls the +load methods. Since each extension could have a load method, they should be uniquely named. Assuming that you have SomeClass which is descended from NSObject, the code for the ObjC stub would look something like this:
In your SomeClass+Foo.swift file:
extension SomeClass {
class func loadFoo {
...do your class initialization here...
}
}
In your SomeClass+Foo.m file:
@implementation SomeClass(Foo)
+ (void)load {
[self performSelector:@selector(loadFoo);
}
@end
The first problem I faced was Swift extension can't have stored properties.
First of all, categories in Objective-C also cannot have stored properties (called synthesized properties). The Objective-C code you linked to uses a computed property (it provides explicit getters and setters).
Anyway, getting back to your problem, you cannot assign to your property because you defined it as a read-only property. To define a read-write computed property, you would need to provide a getter and setter like this:
var fixedHeightWhenStatusBarHidden: Bool {
get {
return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue
}
set(newValue) {
objc_setAssociatedObject(self, "FixedNavigationBarSize", NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN))
}
}