I am trying to convert some swift function to objective-c
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
It is a code from Load a UIView from nib in Swift
I thought .h file can be wrote like this
#interface UIView <T: UIView *> (MyCommon)
+ (T) loadFromNib;
#end
And I got error like this
Category of non-parameterized class 'UIView' cannot have type parameters
Is that means it's impossible to implement UIView extension function using Generic in objective C?
That answer I mentioned use a NSObject category. Maybe change that to a UIView category as below.
EDIT
Since you want a MyView see the changes below and try that. Note that in Objective-C you get a warning while Swift will slap you on the wrist for such code ...
#class MyView;
#interface UIView (loadFromNib)
#end
#implementation UIView (LoadFromNib)
// This only returns UIView or subclass ... or nil
+ ( UIView * )loadFromNib:( NSString * ) name
{
// For the adventurous
return [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil].firstObject;
// For the cautious
for ( NSObject * i in [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] )
{
if ( [i isKindOfClass:UIView.class] )
{
return i;
}
}
// For the unsuccessful
return nil;
}
// This returns MyView
+ ( MyView * ) loadMyViewFromNib:( NSString * ) name
{
return [UIView loadFromNib:name];
}
#end
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 created a custom UI view under this facebook tutorial https://facebook.github.io/react-native/docs/native-components-ios.html#content
MyCustomView.h
#interface MyCustomView : UIView
#property (nonatomic, copy) NSString *test;
#end
MyCustomView.m (init method is omitted)
- (void)setTest:(NSString *)test
{
self.test = test;
}
And I expose this property to JS in MyCustomViewManager
MyCustomViewManager.h
#interface ReactVideoViewManager : RCTViewManager
#end
MyCustomViewManager.m
RCT_EXPORT_MODULE()
- (UIView *)view
{
MyCustomView *videoView = [[MyCustomView alloc] init];
return;
}
RCT_EXPORT_VIEW_PROPERTY(test, BOOL);
I used this view in JS as follows:
<MyCustomView style={styles.row} test={true}></MyCustomView>
However, when the MyCustomView object initialized, the setTest method called tens of thousands of times and crashed at last.
Did anyone know the reason?
a working solution might be:
- (void)setTest:(NSString *)test
{
_test = test;
}
Let's say I want to create a bunch of different types of Spaceships. I want to setup a base spaceship class that I can use to create other spaceships with minor differences.
My base class looks like this.
// BaseSpaceship.h
#interface SpaceshipNode : SKSpriteNode
#property NSColor color;
#property CGFloat engineThrust;
+ (id)baseSpaceshipWithImageNamed:(NSString *)name;
#end
// BaseSpaceship.m
#implementation BaseSpaceship
+ (id)baseSpaceshipWithImageNamed:(NSString *)name {
BaseSpaceship *ship = [BaseSpaceship spriteNodeWithImageNamed:name];
ship.color = [NSColor redColor];
ship.engineThrust = 2.0;
return ship;
}
#end
I can create a ship in MyScene.m like this just fine.
BaseSpaceship *baseClass = [BaseSpaceship baseSpaceshipWithImageNamed:#"BaseShip"];
However, I'm not sure how to create a subclass of BaseSpaceship, for example, DestroyerSpaceship. I'm not sure if I should be using static methods or not. The examples I've seen online use static methods to instantiate SKSpriteNodes. This is what I came up with, but it's wrong.
// DestroyerSpaceship.h
#interface DestroyerSpaceship : BaseSpaceship
#property CGFloat missileThrust;
- (id)makeDestroyerSpaceship;
#end
// DestroyerSpaceship.m
#implementation DestroyerSpaceship
- (id)makeDestroyerSpaceship{
DestroyerSpaceship *ship = [DestroyerSpaceship baseSpaceshipWithImageNamed:#"DestroyerShip"];
ship.engineThrust = 2.0;
// ship doesn't have missileThrust, program crashes
ship.missileThrust = 3.0;
return ship;
}
#end
Ultimately, I want to be able to do something like this.
DestroyerSpaceship* a = [DestroyerSpaceship makeDestroyerSpaceship];
EvilSpaceship* b = [EvilSpaceship makeEvilSpaceship];
NiceSpaceship* c = [NiceSpaceship makeNiceSpaceship];
And have them all inherit basic properties and methods from BaseSpaceship.
The answer is less complex than you think. Well, the code might be a bit more complex, but once you have the structure it is most flexible. Creating the different types of spaceships will also be a lot more readable.
You can override the initializer method in the subclass. As a sidenote, use (instancetype) instead of (id) (source: instancetype # NSHipster).
As you are adding custom body sprites to the object, I would opt to subclass SKNode instead of SKSpriteNode (so #interface SpaceshipNode : SKNode instead of #interface SpaceshipNode : SKSpriteNode).
#interface SpaceshipNode : SKNode
#property SKColor * color; // Use SKColor instead of NSColor
#property CGFloat engineThrust;
#end
// ...
#implementation SpaceshipNode
- (instancetype) init {
if (self == [super init]) {
NSLog(#"A new SpaceshipNode was just init'ed.");
// set some default initial values here that all brand-new SpaceshipNodes will inherit
// perhaps create and add a basic body sprite
// SKSpriteNode * body = ...;
// [self addChild:body];
// set thrust
self.engineThrust = 2.0;
}
return self;
}
Then you can subclass and create a new type of spaceship. Awesome!
#interface DestroyerSpaceship : SpaceshipNode
#property CGFloat missileThrust;
#end
#implementation DestroyerSpaceship
- (instancetype) init {
// note that [super init] will call the SpaceshipNode's init method
if (self = [super init]) {
NSLog(#"A new DestroyerSpaceship was just init'ed.");
// add a body sprite
// SKSpriteNode * body = ...;
// [self addChild:body];
// a Destroyer is much faster than your average spaceship
self.engineThrust = 10.0;
// set class specific variables
self.missileThrust = 5.f;
}
return self;
}
Now, you can just call:
SpaceshipNode * newSpaceShip = [SpaceshipNode new]; // short for [[SpaceshipNode alloc] init];
DestroyerSpaceship * newDestroyer = [DestroyerSpaceship new];
These two lines will log the following. The last two lines are caused by the Destroyer, which first calls the SpaceshipNode init, and then the Destroyer-specific init method.
A new SpaceshipNode was just init'ed.
A new SpaceshipNode was just init'ed.
A new DestroyerSpaceship was just init'ed.
And you can even use it like this:
SpaceshipNode * newUnidentifiedVessel = [DestroyerSpaceship new];
if ([newUnidentifiedVessel isKindOfClass:[DestroyerSpaceship class]]) {
NSLog(#"We are under attack! Route power to shields!");
}
- (instancetype)makeDestroyerSpaceship{
if (self = [super baseSpaceshipWithImageNamed:#"DestroyerShip"]) {
self.engineThrust = 2.0;
self.missileThrust = 3.0;
}
return self;
}
I want to use an static function from a protocol in a function:
#implementation IPadPanoramaViewController
- (void)viewDidLoad
{
[self.view addSubview:[PanoramaContent getPanoramaContentByPanoramaItem:[[PanoramaListItem alloc] init]];
[super viewDidLoad];
}
#end
#protocol PanoramaItemProtocol
+ (UIView *) getPanoramaItemBySection;
#end
#implementation PanoramaContent
+ (UIView *) getPanoramaContentByPanoramaItem:(id<PanoramaItemProtocol>) itemKind {
return [itemKind getPanoramaItemBySection]; //here is the problem "unrecognized selector sent to instance"
}
#end
I wish "PanoramaListItem" don't be a NSObject
The problem is that you've defined getPanoramaBySection to be a class method, when you're calling it on an instance. In the protocol declaration, replace the + with a -.
First, static methods can only be sent to a class. If you don't want to create an object, the parameter of getPanoramaContentByPanoramaItem: should be of type Class. You can use something like the following:
+ (UIView *) getPanoramaContentByPanoramaItem:(Class)itemKind {
UIView *v = nil;
if( [itemKind respondsToSelector:#selector(getPanoramaItemBySection)] ) {
v = [itemKind getPanoramaItemBySection];
}
return v;
}
and to send the message:
[self.view addSubview:[PanoramaContent getPanoramaContentByPanoramaItem:[PanoramaListItem class]]];
It's the first time I'm trying to use typedef. Admittedly I don't have a very clear idea of what's going on but my understanding was that the values inside typedef get assigned integers starting with 0. I've tried to use them as integers but I get various warnings and errors. One of them is "[NSCFNumber objectForKey:]: unrecognized selector sent to instance". I don't know how to troubleshoot this. I also haven't written dynamic getters/setters much, so my approach might be wrong. Please help.
// MyView.h
typedef enum
{
STYLE_A,
STYLE_B,
STYLE_C,
STYLE_D
} MyShapeStyle;
#interface MyView : UIView
{
MyShapeStyle shapeStyle;
CALayer *myLayer;
MyLayerDelegate *myLayerDelegate;
}
#property (nonatomic) MyShapeStyle shapeStyle;
#property (nonatomic, retain) CALayer *myLayer;
#property (nonatomic, retain) MyLayerDelegate *myLayerDelegate;
#end
// MyView.m
#import "MyView.h"
#implementation MyView
#dynamic shapeStyle;
#synthesize myLayer;
#synthesize myLayerDelegate;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
// Initialization code
MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];
self.myLayerDelegate = delegate;
CALayer *myLayer = [CALayer layer];
[myLayer setDelegate:delegate];
[self.layer addSublayer:myLayer];
self.myLayer = myLayer;
self.shapeStyle = STYLE_C;
[delegate release];
}
return self;
}
-(MyShapeStyle)shapeStyle
{
return [[self.myLayer valueForKey:#"style"] integerValue];
}
- (void)setShapeStyle:(MyShapeStyle)style
{
[self.myLayer setValue:[NSNumber numberWithInt:style] forKey:#"style"];
}
// MyLayerDelegate.m
-(void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
int id = [[theLayer valueForKey:#"style"] integerValue];
if( id == STYLE_A )
{
}else if ( id == STYLE_B ){
}
}
There is no reason to use valueForKey: in that code; just get/set the various properties directly.
-(MyShapeStyle)shapeStyle
{
return (MyShapeStyle) self.myLayer.style;
}
There is also no need for the #dynamic in that code. That is only needed if you are going to dynamically generate the methods.
As for why the objectForKey: does-not-respond error, there isn't anything in that code that should trigger that. Could be a retain/release issue or it could be a problem in some other code that you haven't shown.