I have the following objective-c category:
#implementation UINavigationBar (Awesome)
static char overlayKey;
static char emptyImageKey;
- (UIView *)overlay
{
return objc_getAssociatedObject(self, &overlayKey);
}
- (void)setOverlay:(UIView *)overlay
{
objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
I tried to translate it into a Swift extension like this:
var AssociatedObjectHandle: UInt8 = 0
extension UINavigationBar {
var overlay:UIView! {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UIView
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
It looks strange because I created a global variable AssociatedObjectHandle and I am not sure what the Character actually does?
Is this the correct way to translate the computed property?
You asked: "Is this the correct way to translate the computed property?"
Ermm, no.
What you are doing is not translating a computed property. It looks like you're using associated objects to simulate a stored property in an extension.
If that is your intention (adding a stored property to your UINavigationBar extension) then the code you posted should work.
The AssociatedObjectHandle variable is some "magic" that makes associated value storage work. You have to pass in the address of some unique variable that tells the Objective-C runtime what key to use when getting/setting associated values for your class. You simply create a static variable and use it's address in the get/set associated value calls.
Related
I am new to Swift and maybe it's a stupid question, but I can't find an answer to it.
I have created an extension:
extension UITextField {
var placeholderLabel: UILabel {
get {
return self.placeholderLabel
}
set {
self.placeholderLabel = newValue
}
}
}
When the property is set, the application crashes.
You can't have a stored property in extension.
Extensions are not allowed to add a property to existing class because adding a property structure of the class will change. And because Objective C, Swift or any other programming language that am aware of could not afford it, it won't allow you to add the stored property to extension.
Isn't there any work around then ??
This is what you can do to save the label as stored property in your extension :)
import Foundation
import UIKit
fileprivate var ascociatedObjectPointer : UInt8 = 99
extension UITextField {
var myLabel : UILabel {
get {
return objc_getAssociatedObject(self, &ascociatedObjectPointer) as! UILabel
}
set {
objc_setAssociatedObject(self, &ascociatedObjectPointer, myLabel, .OBJC_ASSOCIATION_RETAIN)
}
}
}
How it works ??
Simple by writing setter and getter for the variable which you are posing or pretending to be stored property and by internally holding a pointer which has nothing to do with the existing class, hence it won't affect the structure of existing class.
Hope it helps.
You can use NSMapTable like this:
extension UITextField {
private static var placeholderLabelMap: NSMapTable<UITextField, UILabel> = .weakToStrongObjects()
var placeholderLabel: UILabel? {
get {
return UITextField.placeholderLabelMap.object(forKey: self)
}
set {
UITextField.placeholderLabelMap.setObject(newValue, forKey: self)
}
}
}
The advantage of Sandeep's answer might be thread safety. You can see this Stack Overflow topic for comparison between the approaches.
What is the equivalent of the following Objective-C code in Swift?
#property (nonatomic, assign, getter = isOpen) BOOL open;
Specifically, how does one declare a variable in Swift to synthesize the getter with a custom name?
Furthermore, how can you subsequently override the implementation of the getter and setter?
Your assumption was close, but a few things could be changed. I will try to help you get as close as possible to the Objective-C version.
First of all, the nonatomic and assign are irrelevant in swift. That leaves us with
#property (getter = isOpen) BOOL open;
Since properties in swift are just instance variables, the swift translation would be as follows.
var open:Bool
Although this has the same basic functionality as the Objective-C version, it is lacking the named getter (isOpen). Unfortunately, there is no direct translation to swift for this (yet). You could use a custom getter and setter.
var open:Bool {
get {
// custom getter
}
set {
// custom setter
}
}
A rather crude work around would be to make another function literally called isOpen that would act as a getter.
func isOpen() -> Bool { return self.open }
In conclusion, what you are asking is only slightly possible, but hopefully in later releases of swift can become a reality.
var open: Bool {
#objc(isOpen)
get {
// custom getter
}
set {
// custom setter
}
}
Leads to this generated header:
SWIFT_CLASS("_TtC11SwiftToObjC9TestClass")
#interface TestClass : NSObject
#property (nonatomic, getter=isOpen) BOOL open;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
As a remarq, for the setter you need to repeat the #objc directive:
#objc( setOpen:) set { self.open = newValue }
Don't forguet the semi-column.
A particular thing is that, doing this, self.open will call the setter/getter itself and produce an infinite loop.
In Obj-C you fix it using self->open. How do this if swift?
I'm trying to play a little bit with swift and iOS 8.
The question is, I'm trying to create this setter in a view controller:
- (void)setViewController:(UIViewController *)viewController
{
_viewController = viewController;
method do something
}
This code below is in Objective-C so I'd like to create it in swift.
I think, I should use willSet, but I know how do it if it's defined when you have your own variable, how can I define that willSet method if that property is a ViewController property.
Thanks
Something like this:
var viewController: UIViewController? {
willSet {
//method to do something
}
}
You can actually access what viewController will be set to with the variable "newValue" if you need that in the method call.
Note: I made the property optional here just to get rid of compiler warnings about needing initialization
An alternate way different from #Eric's answer is by using a computed property, which mimics objective-c properties more closely, consisting on defining a pseudo-private data member and a computed property implementing a getter and a setter:
class MyClass {
var _viewController: UIViewController?
var viewController : UIViewController? {
get {
return self._viewController
}
set {
self._viewController = newValue
// Do something else
}
}
}
I defined the property pseudo-private because swift doesn't have access modifiers (yet), so everything is public - but when I see an underscore prefixing a variable or property name, that to me means private (It's a convention I use it a lot in javascript for instance).
I've tried to declare IBOutlet property on extension of class. But it give error as
'var' declaration without getter/setter method not allowed here
class ExampleView : UIView
{
}
extension ExampleView
{
#IBOutlet var btn1, btn2 : UIButton // here I got error.
}
Please any one suggest me correct way to do it?
From Extensions -> Computed Properties in The Swift Programming Language
NOTE
Extensions can add new computed properties, but they cannot add stored
properties, or add property observers to existing properties.
Addition in response to twlkyao's comment: Here is my implementation of the absoluteValue property of a Double
extension Double {
var absoluteValue: Double {
if self >= 0 {
return self
} else {
return -self
}
}
}
// Simple test -> BOTH println() should get called.
var a = -10.0
if (a < 0) {
println("Smaller than Zero")
}
if (a.absoluteValue > 5) {
println("Absolute is > 5")
}
From The Swift Programming Language:
Extensions in Swift can:
Add computed properties and computed static properties
Define instance methods and type methods
Provide new initializers
Define subscripts
Define and use new nested types
Which means you can't add IBOutlets and other stored properties.
If you really want to cheat, you can create global vars or a bookkeeping object which would allow you to query these vars or the object in order to add those properties (and have them be computed properties).
But it seems like it would go against the best practices. I would only do it if there's absolutely no other way.
Is the lazy attribute in Swift equivalent to overriding the getter with a lazy loading pattern in Objective C?
From the docs:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy attribute before its declaration.
So, mostly, yes.
You must always declare a lazy property as a variable (with the var keyword), because its initial value may not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.”
Remember that on Swift you have the option to declare custom getters and setters for your properties:
var name : String?{
get{
return "Oscar"
}
set(newValue){
}
}
Referring to this:
lazy var x = SomeFunction()
The closest equivalent in Objective-C would be something like:
#property BOOL xHasBeenSet;
#property id x;
- (id)x
{
if (!self.xHasBeenSet) {
self.x = SomeFunction();
}
return x;
}
- (void)setX:(id)x
{
_x = x;
self.xHasBeenSet = YES;
}
Here you would only see SomeFunction called the first time you read x, but only if you don't set x first. It's important to note that there is only one code path where the right side gets called and it never resets back to xHasBeenSet = NO.
mainly yes-- it can't be a computed property though