What is the Swift equivalent of the following expression:
#property (strong, nonatomic) UIView *topView;
- (UIView *)topView {
...
}
Is it the following:
var topView: UIView {
get {
...
}
}
If the former is true, is there a way to define an external getter?
I think what you're asking is how to implement something similar to the following:
#property (nonatomic, strong) UIView *topView
- (UIView *)topView {
if (_topView == nil) {
_topView = //...
// configure _topView...
}
return _topView;
}
This lazy property getter is easy to achieve in Swift:
lazy var topView: UIView = {
let view = //...
// configure view...
return view
}()
This results in a read-only variable that is initialised only when first accessed. The Swift code you posted is a computed read-only property which is evaluated every time it is accessed.
Related
I'm trying to achieve/force this safe & clean swift syntax
struct PopButton: View
{
var Label = PopEngineLabel(label:"Hello")
var body: some View
{
Text(Label.label)
}
}
Ojective-c
#interface PopEngineLabel : NSObject
#property (strong, nonatomic) NSString* label;
- (id)initWithLabel:(NSString*)label;
#end
#implementation PopEngineLabel
- (id)initWithLabel:(NSString*)label
{
if ( label )
self.label = label;
else
self.label = #"<null>";
return self;
}
#end
But in the swiftUI code, I get the error
error: value of optional type 'PopEngineLabel?' must be unwrapped to refer to member 'label' of wrapped base type 'PopEngineLabel'
Text(Label.label)
I can remove the errors with
Text(Label?.label ?? "default")
Text(Label!.label)
I'm assuming this is because all objective-c class/instances are implicitly optional.... BUT, the following code makes it non-optional, but crashes at runtime, as it hasn't done my objective-c initialiser (and .label is nil)
struct PopButton: View
{
var Label = PopEngineLabel()
var body: some View
{
Text(Label.label)
}
}
Can I force the user to use a constructor/initialiser AND be a non-optional in swift? (without making an intermediate swift class)
You can use nullability specifiers in Objective-C to tell Swift whether a specific property can be nil or not (and hence whether Swift should treat it as an Optional or not). By default, all reference types in Swift can be null, since they are pointers and hence by default, Swift treats all Obj-C pointer properties as implicitly unwrapped optionals.
However, this won't make the inherited initialiser from NSObject initialise label correctly (or make that initialiser unusable). So you need to assign a default value to label in the default init inherited from NSObject.
#interface PopEngineLabel : NSObject
#property (strong, nonatomic, nonnull) NSString* label;
- (instancetype)initWithLabel:(nonnull NSString*)label;
- (nonnull instancetype)init NS_UNAVAILABLE; // disable default initialiser in Swift
#end
#implementation PopEngineLabel
- (instancetype)init {
self = [super init];
if (self) {
self.label = #"<null>";
}
return self;
}
- (instancetype)initWithLabel:(nonnull NSString*)label {
self = [super init];
if (self) {
if (label) {
self.label = label;
} else {
self.label = #"<null>";
}
}
return self;
}
#end
Couple of other things to bear in mind in Objective-C:
Your init shouldn't return id, but instancetype
You should delegate to super init
I've encountered a bit of a poser involving NSTreeController and KVO. NSTreeController's selectionIndexPaths property is documented as being KVO-observable—and when I observe it directly, it works perfectly. However, if I list NSTreeController's selectionIndexPath as a dependency of some other property, and then try to observe that, the notifications are not fired when one would expect.
Here's the shortest sample code I could come up with to demonstrate what I mean:
import Cocoa
class ViewController: NSViewController {
// Our tree controller
#IBOutlet dynamic var treeController: NSTreeController!
// Some random property on my object; you'll see why it's here later
#objc dynamic var foo: String = "Foo"
// A quick-and-dirty class to give us something to populate our tree with
class Thingy: NSObject {
#objc let name: String
init(_ name: String) { self.name = name }
#objc var children: [Thingy] { return [] }
}
// The property that the tree controller's `Content Array` is bound to
#objc dynamic var thingies: [Thingy] = [Thingy("Foo"), Thingy("Bar")]
// Dependencies for selectionIndexPaths
#objc private static let keyPathsForValuesAffectingSelectionIndexPaths: Set<String> = [
#keyPath(treeController.selectionIndexPaths),
#keyPath(foo)
]
// This property should be dependent on the tree controller's selectionIndexPaths
// (and also on foo)
#objc dynamic var selectionIndexPaths: [IndexPath] {
return self.treeController.selectionIndexPaths
}
// Some properties to store our KVO observations
var observer1: NSKeyValueObservation? = nil
var observer2: NSKeyValueObservation? = nil
// And set up the observations
override func viewDidLoad() {
super.viewDidLoad()
self.observer1 = self.observe(\.selectionIndexPaths) { _, _ in
print("This is only logged when foo changes")
}
self.observer2 = self.observe(\.treeController.selectionIndexPaths) { _, _ in
print("This, however, is logged when the tree controller's selection changes")
}
}
// A button is wired to this; its purpose is to set off the
// KVO notifications for foo
#IBAction func changeFoo(_: Any?) {
self.foo = "Bar"
}
}
In addition, the following setup is done in the storyboard:
Add a tree controller, and connect the view controller's treeController outlet to it.
Bind the tree controller's "Content Array" binding to thingies on the view controller.
Set the tree controller's "Children Key Path" to children.
Create an outline view, and bind its "Content" and "Selection Index Paths" bindings to arrangedObjects and selectionIndexPaths respectively on the tree controller.
Create a button, and point it at the view controller's changeFoo: method.
If you'd like to try it yourself, I've uploaded a sample project here.
The behavior is as follows:
The notification for observer2 is always fired whenever the outline view's (and thus the tree controller's) selection changes, as one would expect.
However, the notification for observer1 is not fired when the outline view's selection changes.
However, observer1's notification is fired when the button is clicked, and foo is changed. This suggests that the property's dependencies are being considered, but just not for this one particular key path.
Using the old-school method with an observeValue(forKeyPath:bla:bla:bla:) override instead of the swank Swift 4 closure-based system seems to behave the same way.
EDIT: Well, it's not Swift's fault! Same thing happens when I write this program in Objective-C:
#interface Thingy: NSObject
#property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
#end
#implementation Thingy
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self == nil) {
return nil;
}
self->_name = name;
return self;
}
- (NSArray *)children { return #[]; }
#end
void *ctxt1 = &ctxt1;
void *ctxt2 = &ctxt2;
#interface ViewController()
#property (nonatomic, strong) IBOutlet NSTreeController *treeController;
#property (nonatomic, copy) NSString *foo;
#property (nonatomic, copy) NSArray *thingies;
#end
#implementation ViewController
+ (NSSet *)keyPathsForValuesAffectingSelectionIndexPaths {
return [NSSet setWithObjects:#"treeController.selectionIndexPaths", #"foo", nil];
}
- (NSArray *)selectionIndexPaths {
return self.treeController.selectionIndexPaths;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.thingies = #[[[Thingy alloc] initWithName:#"Foo"], [[Thingy alloc] initWithName:#"Bar"]];
[self addObserver:self forKeyPath:#"selectionIndexPaths" options:0 context:ctxt1];
[self addObserver:self forKeyPath:#"treeController.selectionIndexPaths" options:0 context:ctxt2];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == ctxt1) {
NSLog(#"This only gets logged when I click the button");
} else if (context == ctxt2) {
NSLog(#"This gets logged whenever the selection changes");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (IBAction)changeFoo:(__unused id)sender {
self.foo = #"Bar";
}
#end
I've been staring at this for a while, and I cannot figure out why directly observing treeController.selectionIndexPaths works, but observing a property that depends on treeController.selectionIndexPaths does not. And since I've generally felt like I had a pretty good handle on KVO and its workings, it is really bugging me that I can't explain this.
Does anyone know the reason for this discrepancy?
Thanks!
I have a custom Objective-C class that contains two CGFloats:
#property (nonatomic, assign) IBInspectable CGFloat minimumConstant NS_REFINED_FOR_SWIFT;
#property (nonatomic, assign) IBInspectable CGFloat maximumConstant NS_REFINED_FOR_SWIFT;
Both are marked as IBInspectable. In the initializer of the class I set both bot NaN as I need to represent a difference between 0 and nothing. To make the class nice to use from Swift I marked both properties as NS_REFINED_FOR_SWIFT and created a Swift extension with the refined implementation:
#IBInspectable public var minimumConstant: CGFloat? {
get {
let constant = __minimumConstant
return constant.isNaN ? nil : constant
}
set {
if let constant = newValue {
__minimumConstant = constant
return
}
__minimumConstant = CGFloat.nan
}
}
#IBInspectable public var maximumConstant: CGFloat? {
get {
let constant = __maximumConstant
return constant.isNaN ? nil : constant
}
set {
if let constant = newValue {
__maximumConstant = constant
return
}
__maximumConstant = CGFloat.nan
}
}
This works great when using my class from code. When using Swift NaN will be mapped to nil and I can use optional unwrapping as usual.
The issue is that as soon as I add NS_REFINED_FOR_SWIFT the Interface Builder will no longer recognize my properties as IBInspectable and does not show them.
Is this a known issue? Is there any workaround for this?
I know default values of IBInspectable-properties can be set as:
#IBInspectable var propertyName:propertyType = defaultValue in Swift. But how do I achieve a similar effect in Objective-C so that I can have default value of some property set to something in Interface Builder?
Since IBInspectable values are set after initWithCoder: and before awakeFromNib:, you can set the defaults in initWithCoder: method.
#interface MyView : UIView
#property (copy, nonatomic) IBInspectable NSString *myProp;
#property (assign, nonatomic) BOOL createdFromIB;
#end
#implementation MyView
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self != nil) {
self.myProp = #"foo";
self.createdFromIB = YES;
}
return self;
}
- (void)awakeFromNib {
if (self.createdFromIB) {
//add anything required in IB-define way
}
NSLog(#"%#", self.myProp);
}
#end
I wrote my code like this. It works pretty well for me, both when designing in the interface builder or running as a app.
#interface MyView : UIView
#property (copy, nonatomic) IBInspectable propertyType *propertyName;
#end
- (void)makeDefaultValues {
_propertyName = defaultValue;
//Other properties...
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self makeDefaultValues];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self makeDefaultValues];
}
return self;
}
I'm using like that
#IBInspectable var propertyNameValue:propertyType?
var propertyName:propertyType { return propertyNameValue ?? defaultValue }
if propertyNameValue has nil, propertyName will return defaultValue.
Why don't use use the macro such as:
#if TARGET_INTERFACE_BUILDER
// .....IB only specific code when being rendered in IB
#endif
???
The prepareForInterfaceBuilder selector may also help you to implement IB specific code.
More info about those 2 points here:
https://developer.apple.com/library/ios/recipes/xcode_help-IB_objects_media/chapters/CreatingaLiveViewofaCustomObject.html
Firstly I tried to override getter and did something like this:
- (UIColor *)borderColor {
return _borderColor ?: [UIColor greenColor];
}
But in that case I received issue about undeclared identifier _borderColor.
Ok, I tried to avoid this issue via custom getter.
- (UIColor *)getBorderColor {
return _borderColor ?: [UIColor greenColor];
}
Actually it's not a proper getter, as we don't point this method as getter. In case we point we'll receive issue about undeclared identifier, that's why we won't.
Then we use this method for getting property value in updateUI method.
Also we have to override setter:
- (void)setBorderColor:(UIColor *)borderColor {
_borderColor = borderColor;
[self updateUI];
}
In my I've defined a CCLayer like this:
#interface MyLayer : CCLayer {
CCLayer * referenceLayer;
}
How should I declare it to use it in +(CCScene *) scene ?
Like this ?
#property (nonatomic, retain) CCLayer *referenceLayer;
Since + (id)scene is a class method, you cannot access an ivar/property from within it. One possible solution is having a static variable in your layer.m file, like in the following snippet:
static CCScene* _scene = nil;
+ (id)scene {
if (_scene == nil) {
_scene = [[CCScene node] retain];
//-- further scene initializaion
}
return _scene;
}
This simple approach has a drawback: you can only have one such layer around.