I wanted to expose an IBOutletCollection for all my UIViewController classes without using inheritance, so I tried like that using the objc runtime, but the is never called. Any idea why ?
#interface UIViewController (MyCategory)
#property (nonatomic, strong) IBOutletCollection(UIView) NSArray<UIView *> * items;
#end
#implementation UIViewController (MyCategory)
- (void)setItems:(NSArray<UIView *> *)items
{
objc_setAssociatedObject(self, "items", items, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray<UIView *> *)items
{
return objc_getAssociatedObject(self, "items");
}
#end
Problem solved, I was not using the IBOutletCollection for the view controller where I enabled the breakpoint :D
Related
I want to provide methods used in several view controllers called in my delegate methods.
For example, I have some CloudKit functionality (I've added this to my own framework, but I don't think thats important), where I want to provide some crash logging.
Previosuly I had a crashLog function in each of my view controllers, which worked fine, but I have a lot of duplicate code.
Therefore I'd like to produce a category with these methods instead.
However I'm having difficulty getting my delegate methods to see these category methods.
Here's my code..
UIViewController+CloudKitDelegates.h
#interface UIViewController (CloudKitDelegates) <iCloudDBDelegate>
#property (weak,nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...;
#end
UIViewController+CloudKitDelegates.m
#import "UIViewController+CloudKitDelegates.h"
#implementation UIViewController (CloudKitDelegates)
#dynamic iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...
{
va_list args;
va_start(args, message);
NSLog(#"%#", [[NSString alloc] initWithFormat:message arguments:args]);
va_end(args);
}
#end
h file - my calling view controller (e.g. My View Controller)
#import "UIViewController+CloudKitDelegates.h"
m file - delegate method
-(NSString*)getDBPath
{
[self.iCloudDBDelegate crashLog: #"testing"];
From this call I'm getting an error ...
'NSInvalidArgumentException', reason: '-[MyViewController crashLog:]:
unrecognized selector sent to instance
The error is showing that my calling view controller called MyViewController doesn't have the crashLog method, which I have in my category.
Any ideas where I'm going wrong ?
The problem: identical method crashLog: in multiple classes, for example
#interface ViewController : UIViewController
#end
#implementation ViewController
- (void)someMethod {
[self crashLog:#"error"];
}
-(void)crashLog:(NSString *)message {
NSLog(#"%#", message);
}
#end
Solution A: move crashLog: to a common superclass (or a category on superclass UIViewController)
#interface CommonViewController : UIViewController
-(void)crashLog:(NSString *)message;
#end
#implementation CommonViewController
-(void)crashLog:(NSString *)message {
NSLog(#"%#", message);
}
#end
#interface ViewController : CommonViewController
#end
#implementation ViewController
- (void)someMethod {
[self crashLog:#"error"];
}
#end
Solution B: move crashLog: to a delegate and protocol
#protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
#end
#interface DelegateClass : AnyClass <ICloudDBDelegate>
#end
#implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(#"%#", message);
}
#end
#interface ViewController : UIViewController
#end
#implementation ViewController
#property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.iCloudDBDelegate = appDel.iCloudDBDelegate;
}
- (void)someMethod {
[self.iCloudDBDelegate crashLog:#"error"];
}
#end
#interface AppDelegate : UIResponder <UIApplicationDelegate, AppDelProtocolDelegate, iCloudDBDelegate>
#property (strong, nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
#end
#implementation AppDelegate
- (id<iCloudDBDelegate>)iCloudDBDelegate {
if (!_iCloudDBDelegate) {
_iCloudDBDelegate = [[DelegateClass alloc] init];
}
return _iCloudDBDelegate;
}
#end
Now we have new problem: property iCloudDBDelegate in multiple classes
Solution B + A: move crashLog to a delegate, move iCloudDBDelegate property to a superclass
#protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
#end
#interface DelegateClass : AnyClass <ICloudDBDelegate>
#end
#implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(#"%#", message);
}
#end
#interface CommonViewController : UIViewController
#property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
#end
#implementation CommonViewController
#end
#interface ViewController : CommonViewController
#end
#implementation ViewController
- (void)someMethod {
[self.iCloudDBDelegate crashLog:#"error"];
}
#end
Solution C:
Another approach is a singleton object like NSUserDefaults.standardUserDefaults or NSFontManager.sharedFontManager: CloudDBManager.sharedCloudDBManager. No category or protocol required, just include CloudDBManager.h and use CloudDBManager.sharedCloudDBManager from everywhere.
#interface CloudDBManager : NSObject
#property(class, readonly, strong) CloudDBManager *sharedCloudDBManager;
-(void)crashLog:(NSString *)message;
#end
#implementation CloudDBManager
+ (CloudDBManager *)sharedCloudDBManager {
static CloudDBManager *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[CloudDBManager alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
-(void)crashLog:(NSString *)message {
NSLog(#"%#", message);
}
#end
#interface ViewController : CommonViewController
#end
#implementation ViewController
- (void)someMethod {
[CloudDBManager.sharedCloudDBManager crashLog:#"error"];
}
#end
(I've added this to my own framework, but I don't think thats important)
Yep, that's the typical problem. You've failed to include -ObjC in the link flags.
See Building Objective-C static libraries with categories. This applies to frameworks as well.
ObjC does not create linker symbols for methods. It can't, they're not resolved until runtime. So the category methods aren't seen by the linker as "missing" and it doesn't bother linking the relevant compile unit. This is an important optimization that keeps you from linking all of a massive C library just because you use one function in it, but Objective-C categories break some of the linker's assumptions. The compiler saw the definition (via the header), but the linker didn't care, so there's no error until runtime.
The -ObjC flag says "this C-looking compile unit is actually Objective-C; link all of it even if you don't think you need to."
I have something similar to the following structure in my code:
// Model
#interface Child : NSObject
#property (nonatomic, assign) CGPoint position;
#end
#interface Father : NSObject
#property (nonatomic, strong) NSArray <Child *> *children; // (0..n)
#end
// ViewModel
#interface FatherViewModel : NSObject
#property (nonatomic, strong) Father *father;
#property (nonatomic, assign) CGPoint averagePositionOfChildren;
#end
During execution, number of children in each Father member can change (in this case I recreate the whole NSArray) and position of each Child can also change.
Does any elegant solution in ReactiveCocoa exist to map the dynamic number of children positions in the model to averagePositionOfChildren in FatherViewModel?
Yes, I can see you choose two strategies:
1. Use properties
Use a MutableProperty for children and then create a mapped property for averagePositionOfChildren.
2. Use KVO
Alternatively, you can use KVO to watch changes in children. I.e., you can create a DynamicProperty.
Note that both scenarios would force you to recreate the whole array, as you already noted.
If anyone is interested, here follows my own solution:
#interface FatherViewModel()
#property (nonatomic, strong) RACDisposable *averageBindingDisposable;
#end
#implementation FatherViewModel
- (instanceType) init {
self = [super init];
...
...
RACSignal *signalOfSignals = [RACObserve(self, father.children)
map:^RACSignal *(NSArray <Child *> *setOfChildren) {
NSArray <RACSignal *> *arrayOfSignal = [[[setOfChildren rac_sequence]
map:^RACSignal *(Child *child) {
return RACObserve(child, position);
}]
array];
return [RACSignal combineLatest:arrayOfSignal];
}];
[self.averageBindingDisposable dispose];
self.averageBindingDisposable = [[[signalOfSignals flatten]
map:^NSValue *(RACTuple *tuple) {
NSArray <NSValue *> *arrayOfWrappedCGPoints = tuple.allObjects;
CGPoint avg = CGPointZero;
// Compute here the average of the points in the array
return [NSValue valueWithPoint: avg];
}]
setKeyPath:#"averagePositionOfChildren" onObject: self];
}];
return self;
}
...
#end
I'm relatively new to ARC. I'm making an UIView subclass, that will have two labels (title and subtitle). I don't want to publicly expose the labels as properties, only their text.
I'm currently using this:
#interface MyView : UIView
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *subtitle;
#end
#implementation MyView
{
UILabel *_titleLabel;
UILabel *_subtitleLabel;
}
- (void)setTitle:(NSString *)title
{
[_titleLabel setText:title];
}
- (NSString *)title
{
return [_titleLabel text];
}
- (void)setSubtitle:(NSString *)subtitle
{
[_subtitleLabel setText:title];
}
- (NSString *)subtitle
{
return [_subtitleLabel text];
}
#end
Are my two #properties correctly declared? Should I use the strong, weak or any other qualifier? And why?
If you are going to work with setter / getter, I think the appropiate tag would be the readwrite. strong weak retain etc apply when the property is the setter/getter for an instance variable.
That's what I have :
Class A :
#import "ppCore.h"
#interface ppApplication : NSApplication {
ppCore* core;
}
#property (assign) ppCore* core;
#end
#implementation ppApplication
#synthesize core;
- (void)awakeFromNib
{
[self setCore:[[[ppCore alloc] init] retain]];
}
Class B :
#import "someObject.h"
#import "anotherObject.h"
#interface ppCore : NSObject<NSApplicationDelegate> {
ppSomeObject* someObject;
ppAnotherObject* anotherObject;
}
#property (assign) ppSomeObject* someObject;
#property (assign) ppAnotherObject* anotherObject;
#end
#implementation ppCore
#synthesize someObject, anotherObject;
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[self setSomeObject:[[ppSomeObject alloc] init]];
[self setAnotherObject:[[ppAnotherObject alloc] init]];
}
And here's the issue :
AT SOME LATER STAGE, in ppApplication, I'm trying to have access to core.
core is there.
But, when I'm trying to access any of core's elements (e.g. [core someObject]), everything is turning up NULL (I've checked it in the Debugger)...
What am I doing wrong??
Have you tried declaring your objects like this:
#property (nonatomic, retain) ppCore* core;
#property (nonatomic, retain) ppSomeObject* someObject;
#property (nonatomic, retain) ppAnotherObject* anotherObject;
I suggest you remove the whole core thing since you can access your delegate through [[NSApplication sharedApplication] delegate] and move the setting of someObject and anotherObject to the delegate's init method.
Antonio is right, bit you need to manage memory as well,
#import "ppCore.h"
#interface ppApplication : NSApplication {
ppCore* core;
}
#property (nonatomic, retain) ppCore* core;
#end
#implementation ppApplication
#synthesize core;
- (void)awakeFromNib
{
ppCore* tempCore = [[ppCore alloc] init];
[self setCore: tempCore];
[tempCore release];
}
This might help.
Why do you believe - (void)applicationDidFinishLaunching: on your ppCore object is ever getting called? It seems to me you'll have to explicitly invoke it from somewhere.
Ok, so I have this, but it wont work:
#interface UILabel (touches)
#property (nonatomic) BOOL isMethodStep;
#end
#implementation UILabel (touches)
-(BOOL)isMethodStep {
return self.isMethodStep;
}
-(void)setIsMethodStep:(BOOL)boolean {
self.isMethodStep = boolean;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(self.isMethodStep){
// set all labels to normal font:
UIFont *toSet = (self.font == [UIFont fontWithName:#"Helvetica" size:16]) ? [UIFont fontWithName:#"Helvetica-Bold" size:16] : [UIFont fontWithName:#"Helvetica" size:16];
id superView = self.superview;
for(id theView in [(UIView *)superView subviews])
if([theView isKindOfClass:[UILabel class]])
[(UILabel *)theView setFont:[UIFont fontWithName:#"Helvetica" size:16]];
self.font = toSet;
}
}
#end
If I take out the getter and setter methods then it doesn't work it tells me I need to create some getter and setter methods (or use #synthesize - but putting #synthesize in the #implementation throws an error too). But with the getter and setter methods I get an EXC_BAD_ACCESS and a crash. Any ideas? Thanks
Tom
It is not possible to add members and properties to an existing class via a category — only methods.
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html
One possible workaround is to write "setter/getter-like" methods, that uses a singleton to save the variables, that would had been the member.
-(void)setMember:(MyObject *)someObject
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
[dict setObject:someObject forKey:self];
}
-(MyObject *)member
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
return [dict objectforKey:self];
}
or — of course — write a custom class, that inherits from UILabel
Note that nowadays an associated object can be injected during runtime. The Objective C Programming Language: Associative References
Checked all answers and did not find the most common solution:
#import <objc/runtime.h>
static void const *key;
#interface ClassName (CategoryName)
#property (nonatomic) BOOL myProperty;
#end
#implementation ClassName (CategoryName)
- (BOOL)myProperty {
return [objc_getAssociatedObject(self, key) boolValue];
}
- (void)setMyProperty:(BOOL)value {
objc_setAssociatedObject(self, key, #(value), OBJC_ASSOCIATION_RETAIN);
}
#end
swift:
private struct AssociatedKeys {
static var keyName = "keyName"
}
extension Foo {
var bar: Any! {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.keyName)
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.keyName , newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
There is actually a way, which may not be ideal, but does work.
For it to work, you will need to create a category for a class X and can only be used on subclasses of the same X (e.g. category UIView (Background) can be used with class MyView : UIView, but not directly with UIView)
// UIView+Background.h
#interface UIView (Background)
#property (strong, nonatomic) NSString *hexColor;
- (void)someMethodThatUsesHexColor;
#end
// UIView+Background.h
#implementation UIView (Background)
#dynamic hexColor; // Must be declared as dynamic
- (void)someMethodThatUsesHexColor {
NSLog(#"Color %#", self.hexColor);
}
#end
Then
// MyView.m
#import "UIView+Background.h"
#interface MyView : UIView
#property (strong, nonatomic) NSString *hexColor;
#end
#implementation MyView ()
- (void)viewDidLoad {
[super viewDidLoad];
[self setHexColor:#"#BABACA"];
[self someMethodThatUsesHexColor];
}
#end
Using this method, you will need to "redeclare" your properties, but after that, you can do all of its manipulation inside your category.
You could inject an associated object during runtime.
#import <objc/runtime.h>
#interface UIView (Private)
#property (nonatomic, assign) CGPoint initialTouchPoint;
#property (nonatomic, strong) UIWindow *alertWindow;
#end
#implementation UIView (Private)
#dynamic initialTouchPoint, alertWindow;
- (CGPoint)initialTouchPoint {
return CGPointFromString(objc_getAssociatedObject(self, #selector(initialTouchPoint)));
}
- (void)setInitialTouchPoint:(CGPoint)initialTouchPoint {
objc_setAssociatedObject(self, #selector(initialTouchPoint), NSStringFromCGPoint(initialTouchPoint), OBJC_ASSOCIATION_RETAIN);
}
- (void)setAlertWindow:(UIWindow *)alertWindow {
objc_setAssociatedObject(self, #selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIWindow *)alertWindow {
return objc_getAssociatedObject(self, #selector(alertWindow));
}
#end
EDIT: Warning: This property would have a unique value for all the instances of the class.
This worked for me, but only because I had only one instance of this class in my app.
#import <AVFoundation/AVFoundation.h>
#interface AVAudioPlayer (AstroAVAudioPlayer)
#property (nonatomic) BOOL redPilot;
#end
#import "AVAudioPlayer+AstroAVAudioPlayer.h"
#implementation AVAudioPlayer (AstroAVAudioPlayer)
BOOL _redPilot;
-(void) setRedPilot:(BOOL)redPilot
{
_redPilot = redPilot;
}
-(BOOL) redPilot
{
return _redPilot;
}
#end
A solution that I found to this was to just give each object that you want flagged a unique tag.
I made a UILabel category to add custom fonts to all my labels but on some i wanted them to be bold so i did this ->
- (void) layoutSubviews {
[super layoutSubviews];
[self addCustomFont];
}
- (void) addCustomFont {
if (self.tag == 22) {
[self setFont:[UIFont fontWithName:SEGOE_BOLD size:self.font.pointSize]];
}else{
[self setFont:[UIFont fontWithName:SEGOE_LIGHT size:self.font.pointSize]];
}
}
It seems as if since Xcode 7 (7.0.1, 7A1001), properties are supported in categories. I noticed that Xcode generates categories now for Core Data subclasses.
For example, I got the files:
Location+CoreDataProperties.h
#import "Location.h"
NS_ASSUME_NONNULL_BEGIN
#interface Location (CoreDataProperties)
#property (nullable, nonatomic, retain) NSNumber *altitude;
#property (nullable, nonatomic, retain) NSNumber *latitude;
#property (nullable, nonatomic, retain) NSNumber *longitude;
#end
NS_ASSUME_NONNULL_END
Location+CoreDataProperties.m
#import "Location+CoreDataProperties.h"
#implementation Location (CoreDataProperties)
#dynamic altitude;
#dynamic latitude;
#dynamic longitude;
#end
So looks like properties in categories might work now. I haven't tested on non-Core Data classes.
What I've noticed is that they do include the category file back into the original class:
Location.h
#interface Location : NSManagedObject
#end
#import "Location+CoreDataProperties.h"
This allows the original class to edit the properties specified by the category.