Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adding UIBarButtonItem RACCommand Support. #406

Merged
merged 3 commits into from

3 participants

@KyleLeneau

This commit references issue #403

...ork/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
((12 lines not shown))
+#import <ReactiveCocoa/NSObject+RACPropertySubscribing.h>
+
+#import <objc/runtime.h>
+
+static void * UIControlRACCommandKey = &UIControlRACCommandKey;
+
+@implementation UIBarButtonItem (RACCommandSupport)
+
+- (RACCommand *)rac_command {
+ return objc_getAssociatedObject(self, UIControlRACCommandKey);
+}
+
+- (void)setRac_command:(RACCommand *)command {
+ objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ self.enabled = command != nil ? command.canExecute : YES;
@joshaber Owner

Style: surround the ternary expression with parens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ork/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
((16 lines not shown))
+static void * UIControlRACCommandKey = &UIControlRACCommandKey;
+
+@implementation UIBarButtonItem (RACCommandSupport)
+
+- (RACCommand *)rac_command {
+ return objc_getAssociatedObject(self, UIControlRACCommandKey);
+}
+
+- (void)setRac_command:(RACCommand *)command {
+ objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ self.enabled = command != nil ? command.canExecute : YES;
+
+ if (command == nil) return;
+
+ RAC(self.enabled) = RACAbleWithStart(command, canExecute);
@joshaber Owner

The WithStart part of this doesn't seem necessary since you set it directly above.

@jspahrsummers Owner

Why not only have this line, instead the one above?

Also, this binding needs to be disposed of if the rac_command is later changed – otherwise, two commands can be bound to this property. So this will need to use -toProperty:onObject: manually, or else subscribeNext:.

What do you mean by "Why not only have this line, instead the one above?"?

@jspahrsummers Owner

The explicit setting of self.enabled above can be omitted if we just use this binding (except for the command == nil case).

Got it. As for the disposing of the signal when setting a new command should I use -toProperty:onObject: to check for an already bound signal and remove it somehow?

@jspahrsummers Owner

Well, -toProperty:onObject: behaves like RAC(), except that it returns a disposable representing the binding. We probably need to associate the disposable with the UIBarButtonItem, then dispose of it if the rac_command is changed later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@joshaber joshaber was assigned
@joshaber
Owner

@KyleLeneau Thanks! Just a few comments and it'd be great if it could be unit tested. :metal:

@KyleLeneau

@joshaber I agree the testing. I created an issue #405 that would help address the testing. With these UIBarButtonItem changes I can see a couple of scenarios to test for upfront.

...ork/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
((19 lines not shown))
+static void * UIControlRACCommandSignalKey = &UIControlRACCommandSignalKey;
+
+@implementation UIBarButtonItem (RACCommandSupport)
+
+- (RACCommand *)rac_command {
+ return objc_getAssociatedObject(self, UIControlRACCommandKey);
+}
+
+- (void)setRac_command:(RACCommand *)command {
+ objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ if (command == nil) return;
+
+ // Check for stored signal
+ RACDisposable *existingSignal = objc_getAssociatedObject(self, UIControlRACCommandSignalKey);
+ if (existingSignal != nil) {
@joshaber Owner
joshaber added a note

Don't need to bother with the nil check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ork/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
((24 lines not shown))
+ return objc_getAssociatedObject(self, UIControlRACCommandKey);
+}
+
+- (void)setRac_command:(RACCommand *)command {
+ objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ if (command == nil) return;
+
+ // Check for stored signal
+ RACDisposable *existingSignal = objc_getAssociatedObject(self, UIControlRACCommandSignalKey);
+ if (existingSignal != nil) {
+ // Remove the old signal to add a new one
+ [existingSignal dispose];
+ }
+
+ RACDisposable *newSignal = [RACAbleWithStart(command, canExecute) toProperty:@"enabled" onObject:self];
@joshaber Owner
joshaber added a note

This could use RAC(self.enabled) = ... to avoid having a fragile keypath.

@jspahrsummers Owner

The whole reason we're doing this is to get a disposable.

@joshaber Owner
joshaber added a note

Ah, in that case it can use @keypath to get rid of the string keypath.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@joshaber
Owner

@KyleLeneau Just a few more comments :+1:

@KyleLeneau

Thanks for the comments and follow up, I have made the changes and pushed again.

@joshaber
Owner

Awesome, thanks! :metal:

@joshaber joshaber merged commit daa9f2a into ReactiveCocoa:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
8 ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 27A887D11703DC6800040001 /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */; };
+ 27A887D21703DDEB00040001 /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
5F244771167E5EDE0062180C /* RACPropertySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F24476F167E5EDE0062180C /* RACPropertySubject.h */; settings = {ATTRIBUTES = (Public, ); }; };
5F244772167E5EDE0062180C /* RACPropertySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F24476F167E5EDE0062180C /* RACPropertySubject.h */; settings = {ATTRIBUTES = (Public, ); }; };
5F244773167E5EDE0062180C /* RACPropertySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F244770167E5EDE0062180C /* RACPropertySubject.m */; };
@@ -358,6 +360,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+RACCommandSupport.h"; sourceTree = "<group>"; };
+ 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+RACCommandSupport.m"; sourceTree = "<group>"; };
5F24476F167E5EDE0062180C /* RACPropertySubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACPropertySubject.h; sourceTree = "<group>"; };
5F244770167E5EDE0062180C /* RACPropertySubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACPropertySubject.m; sourceTree = "<group>"; };
5F2447AC167E87C50062180C /* RACObservablePropertySubjectSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACObservablePropertySubjectSpec.m; sourceTree = "<group>"; };
@@ -891,6 +895,8 @@
88F44265153DCAC50097B4C3 /* UITextField+RACSignalSupport.m */,
A1FCC27215666AA3008C9686 /* UITextView+RACSignalSupport.h */,
A1FCC27315666AA3008C9686 /* UITextView+RACSignalSupport.m */,
+ 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */,
+ 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */,
);
name = "UIKit Support";
sourceTree = "<group>";
@@ -1183,6 +1189,7 @@
D077A16E169B740200057BB1 /* RACEvent.h in Headers */,
D099E819169E05D00000A975 /* NSObject+RACObservablePropertySubject.h in Headers */,
6EA0C08216F4AEC1006EBEB2 /* NSObject+RACDeallocating.h in Headers */,
+ 27A887D21703DDEB00040001 /* UIBarButtonItem+RACCommandSupport.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1490,6 +1497,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 27A887D11703DC6800040001 /* UIBarButtonItem+RACCommandSupport.m in Sources */,
8882D4601673B0450080E7CD /* RACBlockTrampoline.m in Sources */,
88F440BA153DAD570097B4C3 /* RACCommand.m in Sources */,
88F440B7153DAD320097B4C3 /* RACSwizzling.m in Sources */,
View
1  ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h
@@ -45,6 +45,7 @@
#import <ReactiveCocoa/UIControl+RACSignalSupport.h>
#import <ReactiveCocoa/UITextField+RACSignalSupport.h>
#import <ReactiveCocoa/UITextView+RACSignalSupport.h>
+#import <ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h>
#elif TARGET_OS_MAC
#import <ReactiveCocoa/EXTKeyPathCoding.h>
#import <ReactiveCocoa/NSControl+RACCommandSupport.h>
View
22 ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h
@@ -0,0 +1,22 @@
+//
+// UIBarButtonItem+RACCommandSupport.h
+// ReactiveCocoa
+//
+// Created by Kyle LeNeau on 3/27/13.
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class RACCommand;
+
+@interface UIBarButtonItem (RACCommandSupport)
+
+// Sets the control's command. When the control is clicked, the command is
+// executed with the sender of the event. The control's enabledness is bound
+// to the command's `canExecute`.
+//
+// Note: this will reset the control's target and action.
+@property (nonatomic, strong) RACCommand *rac_command;
+
+@end
View
56 ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
@@ -0,0 +1,56 @@
+//
+// UIBarButtonItem+RACCommandSupport.m
+// ReactiveCocoa
+//
+// Created by Kyle LeNeau on 3/27/13.
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+//
+
+#import "UIBarButtonItem+RACCommandSupport.h"
+#import <ReactiveCocoa/RACCommand.h>
+#import <ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h>
+#import <ReactiveCocoa/NSObject+RACPropertySubscribing.h>
+#import <ReactiveCocoa/RACSignal+Operations.h>
+#import <ReactiveCocoa/RACDisposable.h>
+#import <ReactiveCocoa/EXTKeyPathCoding.h>
+#import <objc/runtime.h>
+
+static void * UIControlRACCommandKey = &UIControlRACCommandKey;
+static void * UIControlRACCommandSignalKey = &UIControlRACCommandSignalKey;
+
+@implementation UIBarButtonItem (RACCommandSupport)
+
+- (RACCommand *)rac_command {
+ return objc_getAssociatedObject(self, UIControlRACCommandKey);
+}
+
+- (void)setRac_command:(RACCommand *)command {
+ objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ if (command == nil) return;
+
+ // Check for stored signal in order to remove it and add a new one
+ RACDisposable *existingSignal = objc_getAssociatedObject(self, UIControlRACCommandSignalKey);
+ [existingSignal dispose];
+
+ RACDisposable *newSignal = [RACAbleWithStart(command, canExecute) toProperty:@keypath(self.enabled) onObject:self];
+ objc_setAssociatedObject(self, UIControlRACCommandSignalKey, newSignal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ [self rac_hijackActionAndTargetIfNeeded];
+}
+
+- (void)rac_hijackActionAndTargetIfNeeded {
+ SEL hijackSelector = @selector(rac_commandPerformAction:);
+ if (self.target == self && self.action == hijackSelector) return;
+
+ if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action.");
+
+ self.target = self;
+ self.action = hijackSelector;
+}
+
+- (void)rac_commandPerformAction:(id)sender {
+ [self.rac_command execute:sender];
+}
+
+@end
Something went wrong with that request. Please try again.