Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adding UIBarButtonItem RACCommand Support. #406

Merged
merged 3 commits into from

3 participants

Kyle LeNeau Josh Abernathy Justin Spahr-Summers
Kyle LeNeau

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;
Josh Abernathy 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);
Josh Abernathy Owner

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

Justin Spahr-Summers 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?"?

Justin Spahr-Summers 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?

Justin Spahr-Summers 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
Josh Abernathy joshaber was assigned
Josh Abernathy
Owner

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

Kyle LeNeau

@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) {
Josh Abernathy 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];
Josh Abernathy Owner
joshaber added a note

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

Justin Spahr-Summers Owner

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

Josh Abernathy 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
Josh Abernathy
Owner

@KyleLeneau Just a few more comments :+1:

Kyle LeNeau

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

Josh Abernathy
Owner

Awesome, thanks! :metal:

Josh Abernathy joshaber merged commit daa9f2a into from
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.
8 ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj
View
@@ -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 */,
1  ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h
View
@@ -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>
22 ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h
View
@@ -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
56 ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
View
@@ -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.