Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a97362f634ae90ee3cede77056734f15777a89fd 1 parent 44b1363
@steipete authored
View
20 LICENCE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Peter Steinberger <steipete@gmail.com>
+This code is a part of http://pspdfkit.com and has been put under MIT license.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
61 PSMenuItem.h
@@ -0,0 +1,61 @@
+// PSMenuItem.h
+//
+// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com)
+// This code is a part of http://pspdfkit.com and has been put under MIT license.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ This subclass adds support for a block-based action on UIMenuItem.
+ If you are as annoyed about the missing target/action pattern, you will love this.
+
+ If you use PSMenuItem with the classic initWithTitle:selector initializer,
+ this will work and be handled just like a UIMenuItem.
+ */
+@interface PSMenuItem : UIMenuItem
+
+// Initialize PSMenuItem with a block.
+- (id)initWithTitle:(NSString *)title block:(void(^)())block;
+
+// Menu Item can be enabled/disabled. (disable simply hides it from the UIMenuController)
+@property(nonatomic, assign, getter=isEnabled) BOOL enabled;
+
+// Action block.
+@property(nonatomic, copy) void(^block)();
+
+
+/**
+ Installs the menu handler. Needs to be called once per class.
+ (A good place is the +load method)
+
+ Following methods will be swizzled:
+ - canBecomeFirstResponder (if object doesn't already return YES)
+ - canPerformAction:withSender:
+ - methodSignatureForSelector:
+ - forwardInvocation:
+
+ The original implementation will be called if the PSMenuItem selector is not detected.
+
+ @parm object can be an instance or a class.
+*/
++ (void)installMenuHandlerForObject:(id)object;
+
+@end
View
165 PSMenuItem.m
@@ -0,0 +1,165 @@
+// PSMenuItem.m
+//
+// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com)
+// This code is a part of http://pspdfkit.com and has been put under MIT license.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "PSMenuItem.h"
+#import <objc/runtime.h>
+#import <objc/message.h>
+
+// imp_implementationWithBlock changed it's type in iOS6 (XCode 4.5)
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000
+#define PSPDFBlockImplCast (__bridge void *)
+#else
+#define PSPDFBlockImplCast
+#endif
+
+NSString *kMenuItemTrailer = @"ps_performMenuItem";
+
+// Add method + swizzle.
+void PSPDFReplaceMethod(Class c, SEL origSEL, SEL newSEL, IMP impl);
+void PSPDFReplaceMethod(Class c, SEL origSEL, SEL newSEL, IMP impl) {
+ Method origMethod = class_getInstanceMethod(c, origSEL);
+ class_addMethod(c, newSEL, impl, method_getTypeEncoding(origMethod));
+ Method newMethod = class_getInstanceMethod(c, newSEL);
+ if(class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
+ class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
+ }else {
+ method_exchangeImplementations(origMethod, newMethod);
+ }
+}
+
+// Checks for our custom selector.
+BOOL PSPDFIsMenuItemSelector(SEL selector);
+BOOL PSPDFPIsMenuItemSelector(SEL selector) {
+ return [NSStringFromSelector(selector) hasPrefix:kMenuItemTrailer];
+}
+
+@interface PSMenuItem()
+@property(nonatomic, assign) SEL customSelector;
+@end
+
+@implementation PSMenuItem
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Static
+
+/*
+ This might look scary, but it's actually not that bad.
+ We hook into the three methods of UIResponder and NSObject to capture calls to our custom created selector.
+ Then we find the UIMenuController and search for the corresponding PSMenuItem.
+ If the kMenuItemTrailer is not detected, we call the original implementation.
+
+ This all wouldn't be neccesasry if UIMenuController would call our selectors with the UIMenuItem as sender.
+ */
++ (void)installMenuHandlerForObject:(id)object {
+ @autoreleasepool {
+ @synchronized(self) {
+ // object can be both a class or an instance of a class.
+ Class objectClass = class_isMetaClass(object_getClass(object)) ? object : [object class];
+
+ // check if menu handler has been already installed.
+ SEL canPerformActionSEL = NSSelectorFromString(@"pspdf_canPerformAction:withSender:");
+ if (!class_getInstanceMethod(objectClass, canPerformActionSEL)) {
+
+ // add canBecomeFirstResponder if it is not returning YES. (or if we don't know)
+ if (object == objectClass || ![object canBecomeFirstResponder]) {
+ SEL canBecomeFRSEL = NSSelectorFromString(@"pspdf_canBecomeFirstResponder");
+ IMP canBecomeFRIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self) {
+ return YES;
+ }));
+ PSPDFReplaceMethod(objectClass, @selector(canBecomeFirstResponder), canBecomeFRSEL, canBecomeFRIMP);
+ }
+
+ // swizzle canPerformAction:withSender: for our custom selectors.
+ // Queried before the UIMenuController is shown.
+ IMP canPerformActionIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, SEL action, id sender) {
+ return PSPDFPIsMenuItemSelector(action) ? YES : ((BOOL (*)(id, SEL, SEL, id))objc_msgSend)(_self, canPerformActionSEL, action, sender);
+ }));
+ PSPDFReplaceMethod(objectClass, @selector(canPerformAction:withSender:), canPerformActionSEL, canPerformActionIMP);
+
+ // swizzle methodSignatureForSelector:.
+ SEL methodSignatureSEL = NSSelectorFromString(@"pspdf_methodSignatureForSelector:");
+ IMP methodSignatureIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, SEL selector) {
+ if (PSPDFPIsMenuItemSelector(selector)) {
+ return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; // fake it.
+ }else {
+ return (NSMethodSignature *)objc_msgSend(_self, methodSignatureSEL, selector);
+ }
+ }));
+ PSPDFReplaceMethod(objectClass, @selector(methodSignatureForSelector:), methodSignatureSEL, methodSignatureIMP);
+
+ // swizzle forwardInvocation:
+ SEL forwardInvocationSEL = NSSelectorFromString(@"pspdf_forwardInvocation:");
+ IMP forwardInvocationIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, NSInvocation *invocation) {
+ if (PSPDFPIsMenuItemSelector([invocation selector])) {
+ for (PSMenuItem *menuItem in [UIMenuController sharedMenuController].menuItems) {
+ if ([menuItem isKindOfClass:[PSMenuItem class]] && sel_isEqual([invocation selector], menuItem.customSelector)) {
+ [menuItem performBlock]; break; // find corresponding MenuItem and forward
+ }
+ }
+ }else {
+ objc_msgSend(_self, forwardInvocationSEL, invocation);
+ }
+ }));
+ PSPDFReplaceMethod(objectClass, @selector(forwardInvocation:), forwardInvocationSEL, forwardInvocationIMP);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - NSObject
+
+- (id)initWithTitle:(NSString *)title block:(void(^)())block {
+ // Create a unique, still debuggable selector unique per PSMenuItem.
+ NSString *strippedTitle = [[[title componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""] lowercaseString];
+ CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
+ NSString *uuidString = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));
+ CFRelease(uuid);
+ SEL customSelector = NSSelectorFromString([NSString stringWithFormat:@"%@_%@_%@:", kMenuItemTrailer, strippedTitle, uuidString]);
+
+ if((self = [super initWithTitle:title action:customSelector])) {
+ self.customSelector = customSelector;
+ _enabled = YES;
+ _block = [block copy];
+ }
+ return self;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Public
+
+// Nils out action selector if we get disabled; auto-hides it from the UIMenuController.
+- (void)setEnabled:(BOOL)enabled {
+ _enabled = enabled;
+ self.action = enabled ? self.customSelector : NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Private
+
+// Trampuline executor.
+- (void)performBlock {
+ if (_block) _block();
+}
+
+@end
View
311 PSPDFMenuItemDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,311 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 781869D415B4CCFB00FA11BE /* LICENCE in Resources */ = {isa = PBXBuildFile; fileRef = 781869D315B4CCFB00FA11BE /* LICENCE */; };
+ 785B39EE15B4C01300F5DD0A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 785B39ED15B4C01300F5DD0A /* UIKit.framework */; };
+ 785B39F015B4C01300F5DD0A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 785B39EF15B4C01300F5DD0A /* Foundation.framework */; };
+ 785B39F215B4C01300F5DD0A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 785B39F115B4C01300F5DD0A /* CoreGraphics.framework */; };
+ 785B39F815B4C01300F5DD0A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 785B39F615B4C01300F5DD0A /* InfoPlist.strings */; };
+ 785B39FA15B4C01300F5DD0A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 785B39F915B4C01300F5DD0A /* main.m */; };
+ 785B39FE15B4C01300F5DD0A /* PSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 785B39FD15B4C01300F5DD0A /* PSAppDelegate.m */; };
+ 785B3A0415B4C01300F5DD0A /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 785B3A0215B4C01300F5DD0A /* MainStoryboard_iPad.storyboard */; };
+ 785B3A0715B4C01300F5DD0A /* PSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 785B3A0615B4C01300F5DD0A /* PSViewController.m */; };
+ 785B3A1415B4C2AB00F5DD0A /* PSMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 785B3A1315B4C2AB00F5DD0A /* PSMenuItem.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 781869D315B4CCFB00FA11BE /* LICENCE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENCE; sourceTree = SOURCE_ROOT; };
+ 785B39E915B4C01300F5DD0A /* PSPDFMenuItemDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PSPDFMenuItemDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 785B39ED15B4C01300F5DD0A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 785B39EF15B4C01300F5DD0A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 785B39F115B4C01300F5DD0A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 785B39F515B4C01300F5DD0A /* PSPDFMenuItemDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PSPDFMenuItemDemo-Info.plist"; sourceTree = "<group>"; };
+ 785B39F715B4C01300F5DD0A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 785B39F915B4C01300F5DD0A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 785B39FB15B4C01300F5DD0A /* PSPDFMenuItemDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PSPDFMenuItemDemo-Prefix.pch"; sourceTree = "<group>"; };
+ 785B39FC15B4C01300F5DD0A /* PSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSAppDelegate.h; sourceTree = "<group>"; };
+ 785B39FD15B4C01300F5DD0A /* PSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSAppDelegate.m; sourceTree = "<group>"; };
+ 785B3A0315B4C01300F5DD0A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPad.storyboard; sourceTree = "<group>"; };
+ 785B3A0515B4C01300F5DD0A /* PSViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSViewController.h; sourceTree = "<group>"; };
+ 785B3A0615B4C01300F5DD0A /* PSViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSViewController.m; sourceTree = "<group>"; };
+ 785B3A1215B4C2AB00F5DD0A /* PSMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSMenuItem.h; path = ../PSMenuItem.h; sourceTree = "<group>"; };
+ 785B3A1315B4C2AB00F5DD0A /* PSMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PSMenuItem.m; path = ../PSMenuItem.m; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 785B39E615B4C01300F5DD0A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 785B39EE15B4C01300F5DD0A /* UIKit.framework in Frameworks */,
+ 785B39F015B4C01300F5DD0A /* Foundation.framework in Frameworks */,
+ 785B39F215B4C01300F5DD0A /* CoreGraphics.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 785B39DE15B4C01300F5DD0A = {
+ isa = PBXGroup;
+ children = (
+ 785B39F315B4C01300F5DD0A /* PSMenuItemDemo */,
+ 785B39EC15B4C01300F5DD0A /* Frameworks */,
+ 785B39EA15B4C01300F5DD0A /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 785B39EA15B4C01300F5DD0A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 785B39E915B4C01300F5DD0A /* PSPDFMenuItemDemo.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 785B39EC15B4C01300F5DD0A /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 785B39ED15B4C01300F5DD0A /* UIKit.framework */,
+ 785B39EF15B4C01300F5DD0A /* Foundation.framework */,
+ 785B39F115B4C01300F5DD0A /* CoreGraphics.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 785B39F315B4C01300F5DD0A /* PSMenuItemDemo */ = {
+ isa = PBXGroup;
+ children = (
+ 785B3A1215B4C2AB00F5DD0A /* PSMenuItem.h */,
+ 785B3A1315B4C2AB00F5DD0A /* PSMenuItem.m */,
+ 781869D315B4CCFB00FA11BE /* LICENCE */,
+ 785B3A1515B4C2B600F5DD0A /* Demo */,
+ );
+ name = PSMenuItemDemo;
+ path = PSPDFMenuItemDemo;
+ sourceTree = "<group>";
+ };
+ 785B39F415B4C01300F5DD0A /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 785B39F515B4C01300F5DD0A /* PSPDFMenuItemDemo-Info.plist */,
+ 785B39F615B4C01300F5DD0A /* InfoPlist.strings */,
+ 785B39F915B4C01300F5DD0A /* main.m */,
+ 785B39FB15B4C01300F5DD0A /* PSPDFMenuItemDemo-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 785B3A1515B4C2B600F5DD0A /* Demo */ = {
+ isa = PBXGroup;
+ children = (
+ 785B39FC15B4C01300F5DD0A /* PSAppDelegate.h */,
+ 785B39FD15B4C01300F5DD0A /* PSAppDelegate.m */,
+ 785B3A0215B4C01300F5DD0A /* MainStoryboard_iPad.storyboard */,
+ 785B3A0515B4C01300F5DD0A /* PSViewController.h */,
+ 785B3A0615B4C01300F5DD0A /* PSViewController.m */,
+ 785B39F415B4C01300F5DD0A /* Supporting Files */,
+ );
+ name = Demo;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 785B39E815B4C01300F5DD0A /* PSPDFMenuItemDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 785B3A0A15B4C01300F5DD0A /* Build configuration list for PBXNativeTarget "PSPDFMenuItemDemo" */;
+ buildPhases = (
+ 785B39E515B4C01300F5DD0A /* Sources */,
+ 785B39E615B4C01300F5DD0A /* Frameworks */,
+ 785B39E715B4C01300F5DD0A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = PSPDFMenuItemDemo;
+ productName = PSPDFMenuItemDemo;
+ productReference = 785B39E915B4C01300F5DD0A /* PSPDFMenuItemDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 785B39E015B4C01300F5DD0A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = PS;
+ LastUpgradeCheck = 0440;
+ ORGANIZATIONNAME = "Peter Steinberger";
+ };
+ buildConfigurationList = 785B39E315B4C01300F5DD0A /* Build configuration list for PBXProject "PSPDFMenuItemDemo" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 785B39DE15B4C01300F5DD0A;
+ productRefGroup = 785B39EA15B4C01300F5DD0A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 785B39E815B4C01300F5DD0A /* PSPDFMenuItemDemo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 785B39E715B4C01300F5DD0A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 785B39F815B4C01300F5DD0A /* InfoPlist.strings in Resources */,
+ 785B3A0415B4C01300F5DD0A /* MainStoryboard_iPad.storyboard in Resources */,
+ 781869D415B4CCFB00FA11BE /* LICENCE in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 785B39E515B4C01300F5DD0A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 785B39FA15B4C01300F5DD0A /* main.m in Sources */,
+ 785B39FE15B4C01300F5DD0A /* PSAppDelegate.m in Sources */,
+ 785B3A0715B4C01300F5DD0A /* PSViewController.m in Sources */,
+ 785B3A1415B4C2AB00F5DD0A /* PSMenuItem.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 785B39F615B4C01300F5DD0A /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 785B39F715B4C01300F5DD0A /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ 785B3A0215B4C01300F5DD0A /* MainStoryboard_iPad.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 785B3A0315B4C01300F5DD0A /* en */,
+ );
+ name = MainStoryboard_iPad.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 785B3A0815B4C01300F5DD0A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 5.1;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 785B3A0915B4C01300F5DD0A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 5.1;
+ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 785B3A0B15B4C01300F5DD0A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "PSPDFMenuItemDemo/PSPDFMenuItemDemo-Prefix.pch";
+ INFOPLIST_FILE = "PSPDFMenuItemDemo/PSPDFMenuItemDemo-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = 2;
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ 785B3A0C15B4C01300F5DD0A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "PSPDFMenuItemDemo/PSPDFMenuItemDemo-Prefix.pch";
+ INFOPLIST_FILE = "PSPDFMenuItemDemo/PSPDFMenuItemDemo-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = 2;
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 785B39E315B4C01300F5DD0A /* Build configuration list for PBXProject "PSPDFMenuItemDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 785B3A0815B4C01300F5DD0A /* Debug */,
+ 785B3A0915B4C01300F5DD0A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 785B3A0A15B4C01300F5DD0A /* Build configuration list for PBXNativeTarget "PSPDFMenuItemDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 785B3A0B15B4C01300F5DD0A /* Debug */,
+ 785B3A0C15B4C01300F5DD0A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 785B39E015B4C01300F5DD0A /* Project object */;
+}
View
14 PSPDFMenuItemDemo/PSAppDelegate.h
@@ -0,0 +1,14 @@
+//
+// PSAppDelegate.h
+// PSMenuItemDemo
+//
+// Copyright (c) 2012 Peter Steinberger. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface PSAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
View
16 PSPDFMenuItemDemo/PSAppDelegate.m
@@ -0,0 +1,16 @@
+//
+// PSAppDelegate.m
+// PSMenuItemDemo
+//
+// Copyright (c) 2012 Peter Steinberger. All rights reserved.
+//
+
+#import "PSAppDelegate.h"
+
+@implementation PSAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ return YES;
+}
+
+@end
View
41 PSPDFMenuItemDemo/PSPDFMenuItemDemo-Info.plist
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.petersteinberger.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile~ipad</key>
+ <string>MainStoryboard_iPad</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
View
14 PSPDFMenuItemDemo/PSPDFMenuItemDemo-Prefix.pch
@@ -0,0 +1,14 @@
+//
+// Prefix header for all source files of the 'PSMenuItemDemo' target in the 'PSMenuItemDemo' project
+//
+
+#import <Availability.h>
+
+#ifndef __IPHONE_5_0
+#warning "This project uses features only available in iOS SDK 5.0 and later."
+#endif
+
+#ifdef __OBJC__
+ #import <UIKit/UIKit.h>
+ #import <Foundation/Foundation.h>
+#endif
View
14 PSPDFMenuItemDemo/PSViewController.h
@@ -0,0 +1,14 @@
+//
+// PSViewController.h
+// PSMenuItemDemo
+//
+// Copyright (c) 2012 Peter Steinberger. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface PSViewController : UIViewController
+
+- (IBAction)buttonPressed:(UIButton *)button;
+
+@end
View
50 PSPDFMenuItemDemo/PSViewController.m
@@ -0,0 +1,50 @@
+//
+// PSViewController.m
+// PSMenuItemDemo
+//
+// Copyright (c) 2012 Peter Steinberger. All rights reserved.
+// This is code from http://pspdfkit.com.
+//
+
+#import "PSViewController.h"
+#import "PSMenuItem.h"
+
+@implementation PSViewController
+
+// add support for PSMenuItem. Needs to be called once per class.
++ (void)load {
+ [PSMenuItem installMenuHandlerForObject:self];
+}
+
++ (void)initialize {
+ [PSMenuItem installMenuHandlerForObject:self];
+}
+
+- (IBAction)buttonPressed:(UIButton *)button {
+ PSMenuItem *actionItem = [[PSMenuItem alloc] initWithTitle:@"Action 1" block:^{
+ [[[UIAlertView alloc] initWithTitle:@"Action 1" message:@"From a block!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
+ }];
+
+ PSMenuItem *action2Item = [[PSMenuItem alloc] initWithTitle:@"Action 2" block:^{
+ [[[UIAlertView alloc] initWithTitle:@"Action 2" message:@"From a block!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
+ }];
+
+ PSMenuItem *submenuItem = [[PSMenuItem alloc] initWithTitle:@"Submenu..." block:^{
+ [UIMenuController sharedMenuController].menuItems = @[
+ [[PSMenuItem alloc] initWithTitle:@"Back..." block:^{
+ [self buttonPressed:button];
+ }],
+ [[PSMenuItem alloc] initWithTitle:@"Sub 1" block:NULL],
+ [[PSMenuItem alloc] initWithTitle:@"Sub 2" block:^{
+ NSLog(@"Sub 2 pressed.");
+ }]];
+
+ [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
+ }];
+
+ [UIMenuController sharedMenuController].menuItems = @[actionItem, action2Item, submenuItem];
+ [[UIMenuController sharedMenuController] setTargetRect:button.bounds inView:button];
+ [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
+}
+
+@end
View
2  PSPDFMenuItemDemo/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
View
44 PSPDFMenuItemDemo/en.lproj/MainStoryboard_iPad.storyboard
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="2.0" toolsVersion="2549" systemVersion="12A269" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" initialViewController="2">
+ <dependencies>
+ <deployment defaultVersion="1296" identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1498"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="4">
+ <objects>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="3" sceneMemberID="firstResponder"/>
+ <viewController id="2" customClass="PSViewController" sceneMemberID="viewController">
+ <view key="view" contentMode="scaleToFill" id="5">
+ <rect key="frame" x="0.0" y="20" width="768" height="1004"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="MNl-Dh-yK9">
+ <rect key="frame" x="330" y="251" width="109" height="37"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
+ <state key="normal" title="Show Menu">
+ <color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <state key="highlighted">
+ <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="buttonPressed:" destination="2" eventType="touchUpInside" id="GAN-lr-ksD"/>
+ </connections>
+ </button>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ </objects>
+ </scene>
+ </scenes>
+ <simulatedMetricsContainer key="defaultSimulatedMetrics">
+ <simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/>
+ <simulatedOrientationMetrics key="orientation"/>
+ <simulatedScreenMetrics key="destination"/>
+ </simulatedMetricsContainer>
+</document>
View
18 PSPDFMenuItemDemo/main.m
@@ -0,0 +1,18 @@
+//
+// main.m
+// PSMenuItemDemo
+//
+// Created by Peter Steinberger on 7/16/12.
+// Copyright (c) 2012 Peter Steinberger. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "PSAppDelegate.h"
+
+int main(int argc, char *argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([PSAppDelegate class]));
+ }
+}
View
47 README.md
@@ -1,4 +1,47 @@
-PSPDFMenuItem
+PSMenuItem
=============
-A block based UIMenuItem subclass.
+A block based UIMenuItem subclass.
+
+The inflexible @selector based approach in UIMenuItem was driving me crazy.
+I searched quite a while for a block-based UIMenuItem, but couldn't find one.
+So finally, I sat down and wrote my own implementation for [my iOS PDF framework PSKit](http://pspdfkit.com).
+
+If you are as annoyed about the missing target/action pattern, as I am, you will *love* this. [Also read the in-depth article on my website.](http://petersteinberger.com/blog/2012/hacking-block-support-into-uimenuitem/)
+
+## How to use
+``` objective-c
+ PSMenuItem *actionItem = [[PSMenuItem alloc] initWithTitle:@"Action 1" block:^{
+ [[[UIAlertView alloc] initWithTitle:@"Message" message:@"From a block!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
+ }];
+
+ PSMenuItem *submenuItem = [[PSMenuItem alloc] initWithTitle:@"Submenu..." block:^{
+ [UIMenuController sharedMenuController].menuItems = @[
+ [[PSMenuItem alloc] initWithTitle:@"Back..." block:^{
+ [self buttonPressed:button];
+ }],
+ [[PSMenuItem alloc] initWithTitle:@"Sub 1" block:^{
+ NSLog(@"Sub 1 pressed");
+ }]];
+ [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
+ }];
+
+ [UIMenuController sharedMenuController].menuItems = @[actionItem, submenuItem];
+ [[UIMenuController sharedMenuController] setTargetRect:button.bounds inView:button];
+ [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
+```
+
+PSMenuItem uses ARC and is tested with Xcode 4.4 and 4.5DP3 (iOS 4.3+)
+
+The code looks a bit scary and involves swizzling certain methods, but it's actually not that bad. No private API is used, and it's highly unlikely that Apple ever changes something as basic as the UIResponder chain.
+
+## Creator
+
+[Peter Steinberger](http://github.com/steipete)
+[@steipete](https://twitter.com/steipete)
+
+I'd love a thank you tweet if you find this useful.
+
+## License
+
+PSMenuItem is available under the MIT license. See the LICENSE file for more info.
Please sign in to comment.
Something went wrong with that request. Please try again.