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
Related
I hope many of us have same problem of binding to work, specially nested binding.
Scenario :
I have a class Person, with name and role properties. Roles will come from other source.
Person Class:
#interface Person : NSObject
#property (strong) NSString *fullName;
#property (strong) NSString *role;
#end
AppDelegate Class:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (strong) NSArray *persons;
#property (strong) NSArray *roles;
- (IBAction)save:(id)sender;
#end
I have a tableview and a popup button. TableView is bind-ed to ArrayController.
How to bind popupbutton to roles, so that the selected value is updated in objects or Person, i.e. our model ?
The AppDelegate.m :
#implementation AppDelegate
- (id)init
{
self = [super init];
if (self) {
self.roles=#[#"Admin",#"SuperUser",#"Clerk",#"Associate",#"Poen"];
Person *p1=[Person new];
[p1 setFullName:#"Anoop"];
Person *p2=[Person new];
[p2 setFullName:#"Billy"];
Person *p3=[Person new];
[p3 setFullName:#"Steven"];
self.persons=#[p1, p2, p3];
}
return self;
}
- (IBAction)save:(id)sender {
for (Person *p in self.persons) {
NSLog(#"Name : %#",p.fullName);
NSLog(#"role : %#",p.role);
}
}
And the binding goes like this :
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.
I have a hierarchy of model objects which I will be displaying on different type of UITableViewCell subclasses. All decision is made on the fly as to which model object should be used and corresponding UITableViewCell subclass' object is spawned and then set the model object to the UITableViewCell's subclass object so that it can fetch values from it.
My UITableViewCell hierarchy is something like this:
The base class Cell hierarchy:
#interface BaseCell : UITableViewCell
{
Base *baseObj_;
}
#end
The subclass of cell hierarchy:
#interface DerivedCell : BaseCell
{
}
#property (nonatomic, retain) Derived *derivedObject;
#end
#implementation DerivedCell
#synthesize derivedObject = baseObj_;
#end
The base class of Model object:
#interface Base : NSObject
{
NSString *title_;
}
#property (nonatomic, retain) NSString *title;
#end
The subclass of model hierarchy
#interface Derived : Base
{
NSString *detailedText_;
}
#property (nonatomic, retain) NSString *detailedText;
#end
When I do so, I am having errors in this line:
#synthesize derivedObject = baseObj_;
Which reads:
Property 'derivedObject' attempting to use ivar 'baseObj_' declared in super class BaseCell.
Type of property 'derivedObject' (Derived*) does not match type of ivar 'baseObj_' ('Base * __strong')
I want to use properties and synthesize them so that I can leverage the uses of properties (like using dot notation etc.). I have for now used accessors and setters which solves the problem:
#interface DerivedCell : BaseCell
{
}
-(Derived*)derivedObject;
-(void)setDerivedObject:(Derived*)newDerivedObject;
#end
But I was just wondering if I could somehow fix these errors to use the properties only.
Thanks,
Raj
Try the below code I have modified your code a bit as shown below
Since you can assign class Base object to class Derived in #synthesize, it can be achieved by this way, I know you have tried it already, I have tried it with the below code and able to access the variables with dot, try the below code and let me know if it is not working
#interface DerivedCell : BaseCell
{
Derived *derivedObject;
}
#property (nonatomic, retain) Derived *derivedObject;
#end
#implementation DerivedCell
#dynamic derivedObject;
- (void)setDerivedObject:(Base *)baseObj {
if (self.derivedObject == nil) {
derivedObject = [[Derived alloc] init];
}
derivedObject.detailedText = baseObj.title;
}
- (Derived *)derivedObject {
return derivedObject;
}
#interface Derived : Base
{
NSString *detailedText_;
}
#property (nonatomic, retain) NSString *detailedText;
#end
#implementation Derived
#synthesize detailedText = detailedText_;
#end
#interface BaseCell : UITableViewCell
{
Base *baseObj_;
}
#property (nonatomic, retain) Base *baseObj;
#end
#implementation BaseCell
#synthesize baseObj = baseObj_;
#end
#interface Base : NSObject
{
NSString *title_;
}
#property (nonatomic, retain) NSString *title;
#end
#implementation Base
#synthesize title = title_;
#end
Base *b = [[Base alloc] init];
b.title = #"Hello Raj";
BaseCell *bc = [[BaseCell alloc] init];
bc.baseObj = b;
DerivedCell *dc = [[DerivedCell alloc] init];
dc.derivedObject = b;
NSLog(#"Derive dc %#", dc.derivedObject.detailedText);
Another Solution which I have provided has an issue when I checked it
#interface BaseCell : UITableViewCell
{
NSString *baseTitle_;
}
#property (nonatomic, retain) NSString *baseTitle;
#end
#implementation BaseCell
#synthesize baseTitle = baseTitle_;
#end
#interface DerivedCell : BaseCell
{
NSString *derivedTitle_;
}
#property (nonatomic, retain) NSString *derivedTitle;
#implementation DerivedCell
#synthesize derivedTitle = baseTitle;
#end
When I created instance for the class and as shown below
DerivedCell *dCell = [[DerivedCell alloc] init];
dCell.baseTitle = #"Hello";
NSLog(#"%#",dCell.baseTitle);//Output was Hello
NSLog(#"%#",dCell.derivedTitle);//Output was (null)
It didn't assign the value to derivedTitle, If it is working for you please let me know
Another solution with memory referncing
#interface BaseCell : UITableViewCell
{
NSMutableString *baseTitle_;
}
#property (nonatomic, retain) NSMutableString *baseTitle;
#end
#implementation BaseCell
#synthesize baseTitle = baseTitle_;
#end
#interface DerivedCell : BaseCell
{
}
#property (nonatomic, retain) NSMutableString *derivedTitle;
#end
#implementation DerivedCell
#synthesize derivedTitle;
- (id) init
{
if ( self = [super init] )
{
baseTitle_ = [[NSMutableString alloc] init];
derivedTitle = baseTitle_;
}
return self;
}
#end
DerivedCell *dCell = [[DerivedCell alloc] init];
[dCell.baseTitle appendString:#"Hello"];
NSLog(#"baseTitle : %#",dCell.baseTitle);
NSLog(#"derivedTitle :%#",dCell.derivedTitle);
Console Output baseTitle : Hello derivedTitle :Hello
One pattern I've used for situations like this is to re-declare the property in a category on the derived class. The one structural change this approach requires from the code you posted is that it requires a same-named property (or equivalent getter/setter methods) to be defined in the base class. Consider the following snippet:
#interface BaseModel : NSObject
#end
#interface DerivedModel : BaseModel
#end
#interface BaseCell : UITableViewCell
{
BaseModel *baseObj_;
}
#property (nonatomic, retain) BaseModel *modelObject;
#end
#interface DerivedCell : BaseCell
#end
#interface DerivedCell (DowntypedPropertyCategory)
#property (nonatomic, retain) DerivedModel *modelObject;
#end
#implementation BaseModel
#end
#implementation DerivedModel
#end
#implementation BaseCell
#synthesize modelObject = baseObj_;
#end
#implementation DerivedCell
#end
In this pattern, the base class declares the iVar and the base-typed property, and synthesizes the implementation. The derived class declares the downcast-typed property in a category. Being in a category, the compiler won't force you to implement methods for that property. This gets you out of trying to synthesize against a superclass's iVar, instead relying on implementations that exist in the superclass, but declaring them to be of a different type. At runtime, the runtime will just end up calling the superclass methods (since Obj-C method dispatch is based on selector only, and does not have multiple dispatch.) As a result, clients of these properties can do stuff like this without any compile time warnings or errors:
#interface UnrelatedObject : NSObject
#end
#implementation UnrelatedObject
- (void)unrelatedMethod: (DerivedCell*)dc
{
DerivedModel* dm = dc.modelObject;
NSLog(#"dm: %#", dm);
}
#end
Again, the catch/minor difference is that in order for this to work, the base class must define a property of the same name (or equivalent getter/setter methods). That said, the property/methods in the base class could be declared (or in the case of methods, NOT even delayed) and defined in the base class's implementation file only, and thus would not be visible to other files merely including the header.
One other note: by using this approach you're missing out on compile time checks for things like mismatch between the property specifiers ([nonatomic|atomic], [readonly|readwrite], [assign|retain|copy]). I've found this pattern incredibly useful, but there are some potential pitfalls to keep an eye out for.
I hope I understand the question correctly, how about typing the model as id?
#interface BaseCell : UITableViewCell
#property(retain, nonatomic) id model;
#end
#implementation BaseCell
#synthesize model;
#end
Then the derived cells can use whatever model classes they want.
When you initialize an instance variable through synthesize, that variable is not accesible from any class that may inherit it.
It looks like you may have been trying to point synthesize to a public instance variable and I'm not sure if that is possible. It may be trying to declare a new variable with the same name which I'm sure would generate some compiler warnings at the least since that new declaration would hide an existing one and is less accessible.
You could simply write your own getter and setter to expose the instance variable.
- (Base *) baseObj {
return _baseObj;
}
- (void) setBaseObj:(Base *)val {
if( val != _baseObj ) {
[_baseObj release];
_baseObj = [val retain];
}
}
Hope this helps!
The code is probably the best way to see what I am trying to do:
AcInfo.h:
#interface AcInfo : NSManagedObject {
#private
}
#property (nonatomic, retain) NSString *registrationNumber;
#end
AcInfo.m:
#implementation AcInfo
#dynamic registrationNumber;
#end
AnotherClass.h:
#interface AnotherClass : NSObject {
}
#property (nonatomic, retain) AcInfo *detailItem;
#property (nonatomic, retain) IBOutlet UITextField *registrationNumberTextField;
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget;
#end
AnotherClass.m:
#import "AcInfo.h"
#implementation AnotherClass
#synthesize detailItem, registrationNumberTextField;
- (void)viewDidLoad
{
[super viewDidLoad];
registrationNumberTextField.text = #"Test";
// I expect this to set detailItem.registrationNumber to the value of
// registrationNumberTextField.text (Test) but it doesn't change anything!
setDetailItemValueFromUIElement:registrationNumberTextField forAcInfoTarget:detailItem.registrationNumber;
}
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget
{
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
// This doesn't do anything when it returns!
(NSString *)acInfoTarget = (UITextField *)uiElement.text
return;
}
}
#end
In short, I want acInfoTarget to call the getter [detailObject registrationNumber] and the setter [detailObject setRegistrationNumber] in the setDetailItemValueFromUIElement: function...
You can set or read properties by name using
// setter
NSString *propertyName = #"myProperty";
[object setValue:newValue forKey:propertyName];
// getter
id value = [object valueForKey:propertyName];
This is slower than using the normal dot notation, though, and it's frequently (though not always) a sign of poorly-designed code.
Also note that id is a pointer type, so you probably don't actually mean "(id*)".
Your code wants to look something like this, I think:
- (void)setDetailItemValueFromUIElement:(id)uiElement forAcInfoTarget:(NSString*)acInfoTarget {
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
NSString *newValue = ((UITextField*)uiElement).text;
[self.detailItem setValue:newValue forKey:acInfoTarget];
}
}
Properties are just syntax sugar for a couple of accessor methods. They are not, in essence, variables so you shouldn't treat them as such. If you want to affect a property, then what you wanting to do is call a method. So you should pass a id and selector parameter and not pointer to a variable type.
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.