diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj index 3003ae5c15..34438ee06d 100644 --- a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj @@ -30,6 +30,9 @@ 1EC06B18173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */; }; 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, ); }; }; + 3C163DE218FE843D001AA13E /* UIImagePickerController+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C163DE018FE843D001AA13E /* UIImagePickerController+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3C163DE318FE843D001AA13E /* UIImagePickerController+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C163DE118FE843D001AA13E /* UIImagePickerController+RACSignalSupport.m */; }; + 3C80F51F18FFCF810018AE41 /* UIImagePickerControllerRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C80F51D18FFCF810018AE41 /* UIImagePickerControllerRACSupportSpec.m */; }; 4925E806181BCC71000B2FEE /* NSControllerRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4925E805181BCC71000B2FEE /* NSControllerRACSupportSpec.m */; }; 554D9E5D181064E200F21262 /* UIRefreshControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; 554D9E5E181064E200F21262 /* UIRefreshControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */; }; @@ -625,6 +628,9 @@ 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIGestureRecognizer+RACSignalSupport.m"; sourceTree = ""; }; 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+RACCommandSupport.h"; sourceTree = ""; }; 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+RACCommandSupport.m"; sourceTree = ""; }; + 3C163DE018FE843D001AA13E /* UIImagePickerController+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImagePickerController+RACSignalSupport.h"; sourceTree = ""; }; + 3C163DE118FE843D001AA13E /* UIImagePickerController+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImagePickerController+RACSignalSupport.m"; sourceTree = ""; }; + 3C80F51D18FFCF810018AE41 /* UIImagePickerControllerRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImagePickerControllerRACSupportSpec.m; sourceTree = ""; }; 4925E805181BCC71000B2FEE /* NSControllerRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSControllerRACSupportSpec.m; sourceTree = ""; }; 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIRefreshControl+RACCommandSupport.h"; sourceTree = ""; }; 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIRefreshControl+RACCommandSupport.m"; sourceTree = ""; }; @@ -1083,6 +1089,7 @@ 5FAF5261174D4D8E00CAC810 /* UIBarButtonItemRACSupportSpec.m */, 5EE9A79A1760D88500EAF5A2 /* UIButtonRACSupportSpec.m */, D066C795176D262500C242D2 /* UIControlRACSupportSpec.m */, + 3C80F51D18FFCF810018AE41 /* UIImagePickerControllerRACSupportSpec.m */, 5564542318107275002BD2E4 /* UIRefreshControlRACSupportSpec.m */, ); name = iOS; @@ -1378,6 +1385,8 @@ 5F70B2AE17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m */, 1EC06B15173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h */, 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */, + 3C163DE018FE843D001AA13E /* UIImagePickerController+RACSignalSupport.h */, + 3C163DE118FE843D001AA13E /* UIImagePickerController+RACSignalSupport.m */, 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */, 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */, 5F70B2B617AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.h */, @@ -1718,6 +1727,7 @@ D077A16E169B740200057BB1 /* RACEvent.h in Headers */, 6EA0C08216F4AEC1006EBEB2 /* NSObject+RACDeallocating.h in Headers */, 27A887D21703DDEB00040001 /* UIBarButtonItem+RACCommandSupport.h in Headers */, + 3C163DE218FE843D001AA13E /* UIImagePickerController+RACSignalSupport.h in Headers */, 1EC06B17173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h in Headers */, D090768017FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */, 5EE9A7931760D61300EAF5A2 /* UIButton+RACCommandSupport.h in Headers */, @@ -2043,6 +2053,7 @@ 5FAF523E174D4D3200CAC810 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, 5FAF523F174D4D3600CAC810 /* NSNotificationCenterRACSupportSpec.m in Sources */, D090768B17FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m in Sources */, + 3C80F51F18FFCF810018AE41 /* UIImagePickerControllerRACSupportSpec.m in Sources */, 5FAF5240174D4D5600CAC810 /* NSObjectRACDeallocatingSpec.m in Sources */, 5FAF5241174D4D5600CAC810 /* NSObjectRACLiftingSpec.m in Sources */, 5FAF5242174D4D5600CAC810 /* NSObjectRACPropertySubscribingExamples.m in Sources */, @@ -2271,6 +2282,7 @@ D0E9678E1641EF9C00FCFF06 /* RACSequence.m in Sources */, D0E967921641EF9C00FCFF06 /* RACStringSequence.m in Sources */, D013A3F31807B9690072B6CE /* RACDynamicSignal.m in Sources */, + 3C163DE318FE843D001AA13E /* UIImagePickerController+RACSignalSupport.m in Sources */, D0D487041642550100DD7605 /* RACStream.m in Sources */, D013A3DA1807B5ED0072B6CE /* RACErrorSignal.m in Sources */, D0EE284E164D906B006954A4 /* RACSignalSequence.m in Sources */, diff --git a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h index 34e3c42d04..0fcd9a8356 100644 --- a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h +++ b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h @@ -59,6 +59,7 @@ #import "UIControl+RACSignalSupport.h" #import "UIDatePicker+RACSignalSupport.h" #import "UIGestureRecognizer+RACSignalSupport.h" + #import "UIImagePickerController+RACSignalSupport.h" #import "UIRefreshControl+RACCommandSupport.h" #import "UISegmentedControl+RACSignalSupport.h" #import "UISlider+RACSignalSupport.h" diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.h new file mode 100644 index 0000000000..17440774ce --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.h @@ -0,0 +1,33 @@ +// +// UIImagePickerController+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Timur Kuchkarov on 28.03.14. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +#import + +@class RACDelegateProxy; +@class RACSignal; + +@interface UIImagePickerController (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for every new selected image. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is considered +/// undefined behavior. +/// +/// Returns a signal which will send the dictionary with info for the selected image. +/// Caller is responsible for picker controller dismissal. The signal will complete +/// itself when the receiver is deallocated or when user cancels selection. +- (RACSignal *)rac_imageSelectedSignal; + +@end diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.m new file mode 100644 index 0000000000..70d4dc3dde --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIImagePickerController+RACSignalSupport.m @@ -0,0 +1,53 @@ +// +// UIImagePickerController+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Timur Kuchkarov on 28.03.14. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +#import "UIImagePickerController+RACSignalSupport.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import + +@implementation UIImagePickerController (RACSignalSupport) + +static void RACUseDelegateProxy(UIImagePickerController *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIImagePickerControllerDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_imageSelectedSignal { + RACSignal *pickerCancelledSignal = [[self.rac_delegateProxy + signalForSelector:@selector(imagePickerControllerDidCancel:)] + merge:self.rac_willDeallocSignal]; + + RACSignal *imagePickerSignal = [[[[self.rac_delegateProxy + signalForSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)] + reduceEach:^(UIImagePickerController *pickerController, NSDictionary *userInfo) { + return userInfo; + }] + takeUntil:pickerCancelledSignal] + setNameWithFormat:@"%@ -rac_imageSelectedSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return imagePickerSignal; +} + +@end diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIImagePickerControllerRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIImagePickerControllerRACSupportSpec.m new file mode 100644 index 0000000000..84e13eab68 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIImagePickerControllerRACSupportSpec.m @@ -0,0 +1,51 @@ +// +// UIImagePickerControllerRACSupportSpec.m +// ReactiveCocoa +// +// Created by Timur Kuchkarov on 17.04.14. +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// + +#import "UIImagePickerController+RACSignalSupport.h" +#import "RACSignal.h" + +SpecBegin(UIImagePickerControllerRACSupport) + +describe(@"UIImagePickerController", ^{ + __block UIImagePickerController *imagePicker; + + beforeEach(^{ + imagePicker = [[UIImagePickerController alloc] init]; + expect(imagePicker).notTo.beNil(); + }); + + it(@"sends the user info dictionary after confirmation", ^{ + __block NSDictionary *selectedImageUserInfo = nil; + [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { + selectedImageUserInfo = userInfo; + }]; + + NSDictionary *info = @{ + UIImagePickerControllerMediaType: @"public.image", + UIImagePickerControllerMediaMetadata: @{} + }; + [imagePicker.delegate imagePickerController:imagePicker didFinishPickingMediaWithInfo:info]; + expect(selectedImageUserInfo).to.equal(info); + }); + + it(@"cancels image picking process", ^{ + __block BOOL didSend = NO; + __block BOOL didComplete = NO; + [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { + didSend = YES; + } completed:^{ + didComplete = YES; + }]; + + [imagePicker.delegate imagePickerControllerDidCancel:imagePicker]; + expect(didSend).to.beFalsy(); + expect(didComplete).to.beTruthy(); + }); +}); + +SpecEnd