├── .gitignore ├── LICENSE.txt ├── README.markdown ├── demo └── FLBugKitDemo │ ├── FLBugKitDemo.xcodeproj │ └── project.pbxproj │ └── FLBugKitDemo │ ├── FLAppDelegate.h │ ├── FLAppDelegate.m │ ├── FLBugKitDemo-Info.plist │ ├── FLBugKitDemo-Prefix.pch │ ├── FLDetailViewController.h │ ├── FLDetailViewController.m │ ├── FLMasterViewController.h │ ├── FLMasterViewController.m │ ├── en.lproj │ ├── FLDetailViewController_iPad.xib │ ├── FLDetailViewController_iPhone.xib │ ├── FLMasterViewController_iPad.xib │ ├── FLMasterViewController_iPhone.xib │ └── InfoPlist.strings │ └── main.m ├── gendocs.sh └── src ├── FLBugKit.h ├── FLBugKit.m ├── UISplitViewController+FLBugKit.h ├── UISplitViewController+FLBugKit.m ├── UIViewController+FLBugKit.h └── UIViewController+FLBugKit.m /.gitignore: -------------------------------------------------------------------------------- 1 | *xcuserdata/ 2 | *build/ 3 | *.pbxuser 4 | *.xcworkspace 5 | *.pyc 6 | bin/ 7 | *.DS_Store 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Scribd Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## FLBugKit 2 | 3 | FLBugKit is a simple way for users or testers to file bugs from witin an iOS application. 4 | The library will collect various metadata about the application such as version and 5 | a screenshot. The developer also has the ability to specify additional information that 6 | should be collected. All of this data is then attached to an email and addressed 7 | to your bug tracking system's inbox. 8 | 9 | ## Getting Started 10 | 11 | To get started add all the files under `src/` to your project and add the `MessageUI.framework` to 12 | your project. 13 | 14 | On your `UIApplicationDelegate` implement `FLStandardApplicationBugMetadataProtocol` and 15 | import `FLBugKit.h`, `UIViewController+FLBugKit.h` and `UISplitViewController+FLBugKit.h`. 16 | 17 | - (NSString *)userId { 18 | return @"sample user id"; 19 | } 20 | 21 | - (NSString *)defaultBugMetadata { 22 | return @"Information besides screenshot and version information"; 23 | } 24 | 25 | - (NSString *)defaultBugEmailAddress { 26 | return @"mybugemail@myproject.fogbugz.com"; 27 | } 28 | 29 | - (NSString *)defaultBugEmailSubject { 30 | return @"Subject of the bug email"; 31 | } 32 | 33 | - (UIGestureRecognizer *)bugGestureRecognizer { 34 | UILongPressGestureRecognizer *bugGesture = [[[UILongPressGestureRecognizer alloc] init] autorelease]; 35 | bugGesture.minimumPressDuration = 0.7; 36 | bugGesture.numberOfTouchesRequired = 3; 37 | return bugGesture; 38 | } 39 | 40 | - (UIViewController *)activeViewControllerFromRootViewController:(UIViewController *)rootViewController gesture:(UIGestureRecognizer *)gesture { 41 | if ([rootViewController isKindOfClass:[UISplitViewController class]]) { 42 | UISplitViewController *split = (UISplitViewController *)rootViewController; 43 | rootViewController = [split viewControllerTargetedByGesture:gesture]; 44 | } 45 | return [rootViewController findTopMostViewController]; 46 | } 47 | 48 | 49 | There are two ways the bug email can be created. Either programatically: 50 | 51 | [[FLBugKit sharedInstance] presentBugMailerForKeyWindow]; 52 | 53 | or by having `FLBugKit` monitor the gesture provided by `bugGestureRecognizer`: 54 | 55 | [[FLBugKit sharedInstance] startMonitoringWindow:applicationWindow]; 56 | 57 | A good place for `startMonitoringWindow` is in the `application:didFinishLaunchingWithOptions:`. 58 | 59 | Also it is important to note that `FLStandardApplicationBugMetadataProtocol` must 60 | be implemented by the object `[UIApplication sharedApplication]` delegate. `FLBugKit` 61 | assumes that object that implements the `FLStandardApplicationBugMetadataProtocol`. 62 | 63 | ## Demo 64 | 65 | The project under `demo/` is the sample Xcode Universal project setup with FLBugKit. 66 | 67 | ## Authors 68 | 69 | * [Evan Long](https://github.com/evanlong) 70 | * [Jesse Andersen](https://github.com/gotosleep) 71 | 72 | ## License 73 | 74 | FLBugKit is licensed under the MIT License. See LICENSE.txt. 75 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FFFF4591151D019D007ACD71 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFF4590151D019D007ACD71 /* UIKit.framework */; }; 11 | FFFF4593151D019D007ACD71 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFF4592151D019D007ACD71 /* Foundation.framework */; }; 12 | FFFF4595151D019D007ACD71 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFF4594151D019D007ACD71 /* CoreGraphics.framework */; }; 13 | FFFF459B151D019D007ACD71 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFFF4599151D019D007ACD71 /* InfoPlist.strings */; }; 14 | FFFF459D151D019D007ACD71 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF459C151D019D007ACD71 /* main.m */; }; 15 | FFFF45A1151D019D007ACD71 /* FLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45A0151D019D007ACD71 /* FLAppDelegate.m */; }; 16 | FFFF45A4151D019D007ACD71 /* FLMasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45A3151D019D007ACD71 /* FLMasterViewController.m */; }; 17 | FFFF45A7151D019D007ACD71 /* FLDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45A6151D019D007ACD71 /* FLDetailViewController.m */; }; 18 | FFFF45AA151D019D007ACD71 /* FLMasterViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = FFFF45A8151D019D007ACD71 /* FLMasterViewController_iPhone.xib */; }; 19 | FFFF45AD151D019D007ACD71 /* FLMasterViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = FFFF45AB151D019D007ACD71 /* FLMasterViewController_iPad.xib */; }; 20 | FFFF45B0151D019D007ACD71 /* FLDetailViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = FFFF45AE151D019D007ACD71 /* FLDetailViewController_iPhone.xib */; }; 21 | FFFF45B3151D019D007ACD71 /* FLDetailViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = FFFF45B1151D019D007ACD71 /* FLDetailViewController_iPad.xib */; }; 22 | FFFF45C0151D0C87007ACD71 /* FLBugKit.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45BB151D0C87007ACD71 /* FLBugKit.m */; }; 23 | FFFF45C1151D0C87007ACD71 /* UISplitViewController+FLBugKit.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45BD151D0C87007ACD71 /* UISplitViewController+FLBugKit.m */; }; 24 | FFFF45C2151D0C87007ACD71 /* UIViewController+FLBugKit.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF45BF151D0C87007ACD71 /* UIViewController+FLBugKit.m */; }; 25 | FFFF45C4151D1047007ACD71 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFF45C3151D1047007ACD71 /* MessageUI.framework */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | FFFF458C151D019D007ACD71 /* FLBugKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FLBugKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | FFFF4590151D019D007ACD71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | FFFF4592151D019D007ACD71 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | FFFF4594151D019D007ACD71 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 33 | FFFF4598151D019D007ACD71 /* FLBugKitDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FLBugKitDemo-Info.plist"; sourceTree = ""; }; 34 | FFFF459A151D019D007ACD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | FFFF459C151D019D007ACD71 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | FFFF459E151D019D007ACD71 /* FLBugKitDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLBugKitDemo-Prefix.pch"; sourceTree = ""; }; 37 | FFFF459F151D019D007ACD71 /* FLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLAppDelegate.h; sourceTree = ""; }; 38 | FFFF45A0151D019D007ACD71 /* FLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLAppDelegate.m; sourceTree = ""; }; 39 | FFFF45A2151D019D007ACD71 /* FLMasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLMasterViewController.h; sourceTree = ""; }; 40 | FFFF45A3151D019D007ACD71 /* FLMasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLMasterViewController.m; sourceTree = ""; }; 41 | FFFF45A5151D019D007ACD71 /* FLDetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLDetailViewController.h; sourceTree = ""; }; 42 | FFFF45A6151D019D007ACD71 /* FLDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLDetailViewController.m; sourceTree = ""; }; 43 | FFFF45A9151D019D007ACD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/FLMasterViewController_iPhone.xib; sourceTree = ""; }; 44 | FFFF45AC151D019D007ACD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/FLMasterViewController_iPad.xib; sourceTree = ""; }; 45 | FFFF45AF151D019D007ACD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/FLDetailViewController_iPhone.xib; sourceTree = ""; }; 46 | FFFF45B2151D019D007ACD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/FLDetailViewController_iPad.xib; sourceTree = ""; }; 47 | FFFF45BA151D0C87007ACD71 /* FLBugKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FLBugKit.h; path = ../../../src/FLBugKit.h; sourceTree = ""; }; 48 | FFFF45BB151D0C87007ACD71 /* FLBugKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLBugKit.m; path = ../../../src/FLBugKit.m; sourceTree = ""; }; 49 | FFFF45BC151D0C87007ACD71 /* UISplitViewController+FLBugKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UISplitViewController+FLBugKit.h"; path = "../../../src/UISplitViewController+FLBugKit.h"; sourceTree = ""; }; 50 | FFFF45BD151D0C87007ACD71 /* UISplitViewController+FLBugKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UISplitViewController+FLBugKit.m"; path = "../../../src/UISplitViewController+FLBugKit.m"; sourceTree = ""; }; 51 | FFFF45BE151D0C87007ACD71 /* UIViewController+FLBugKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+FLBugKit.h"; path = "../../../src/UIViewController+FLBugKit.h"; sourceTree = ""; }; 52 | FFFF45BF151D0C87007ACD71 /* UIViewController+FLBugKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+FLBugKit.m"; path = "../../../src/UIViewController+FLBugKit.m"; sourceTree = ""; }; 53 | FFFF45C3151D1047007ACD71 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | FFFF4589151D019D007ACD71 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | FFFF45C4151D1047007ACD71 /* MessageUI.framework in Frameworks */, 62 | FFFF4591151D019D007ACD71 /* UIKit.framework in Frameworks */, 63 | FFFF4593151D019D007ACD71 /* Foundation.framework in Frameworks */, 64 | FFFF4595151D019D007ACD71 /* CoreGraphics.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | FFFF4581151D019D007ACD71 = { 72 | isa = PBXGroup; 73 | children = ( 74 | FFFF4596151D019D007ACD71 /* FLBugKitDemo */, 75 | FFFF458F151D019D007ACD71 /* Frameworks */, 76 | FFFF458D151D019D007ACD71 /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | FFFF458D151D019D007ACD71 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | FFFF458C151D019D007ACD71 /* FLBugKitDemo.app */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | FFFF458F151D019D007ACD71 /* Frameworks */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | FFFF45C3151D1047007ACD71 /* MessageUI.framework */, 92 | FFFF4590151D019D007ACD71 /* UIKit.framework */, 93 | FFFF4592151D019D007ACD71 /* Foundation.framework */, 94 | FFFF4594151D019D007ACD71 /* CoreGraphics.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | FFFF4596151D019D007ACD71 /* FLBugKitDemo */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | FFFF45B9151D0C6A007ACD71 /* FLBugKit */, 103 | FFFF459F151D019D007ACD71 /* FLAppDelegate.h */, 104 | FFFF45A0151D019D007ACD71 /* FLAppDelegate.m */, 105 | FFFF45A2151D019D007ACD71 /* FLMasterViewController.h */, 106 | FFFF45A3151D019D007ACD71 /* FLMasterViewController.m */, 107 | FFFF45A5151D019D007ACD71 /* FLDetailViewController.h */, 108 | FFFF45A6151D019D007ACD71 /* FLDetailViewController.m */, 109 | FFFF45A8151D019D007ACD71 /* FLMasterViewController_iPhone.xib */, 110 | FFFF45AB151D019D007ACD71 /* FLMasterViewController_iPad.xib */, 111 | FFFF45AE151D019D007ACD71 /* FLDetailViewController_iPhone.xib */, 112 | FFFF45B1151D019D007ACD71 /* FLDetailViewController_iPad.xib */, 113 | FFFF4597151D019D007ACD71 /* Supporting Files */, 114 | ); 115 | path = FLBugKitDemo; 116 | sourceTree = ""; 117 | }; 118 | FFFF4597151D019D007ACD71 /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | FFFF4598151D019D007ACD71 /* FLBugKitDemo-Info.plist */, 122 | FFFF4599151D019D007ACD71 /* InfoPlist.strings */, 123 | FFFF459C151D019D007ACD71 /* main.m */, 124 | FFFF459E151D019D007ACD71 /* FLBugKitDemo-Prefix.pch */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | FFFF45B9151D0C6A007ACD71 /* FLBugKit */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | FFFF45BA151D0C87007ACD71 /* FLBugKit.h */, 133 | FFFF45BB151D0C87007ACD71 /* FLBugKit.m */, 134 | FFFF45BC151D0C87007ACD71 /* UISplitViewController+FLBugKit.h */, 135 | FFFF45BD151D0C87007ACD71 /* UISplitViewController+FLBugKit.m */, 136 | FFFF45BE151D0C87007ACD71 /* UIViewController+FLBugKit.h */, 137 | FFFF45BF151D0C87007ACD71 /* UIViewController+FLBugKit.m */, 138 | ); 139 | name = FLBugKit; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | FFFF458B151D019D007ACD71 /* FLBugKitDemo */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = FFFF45B6151D019D007ACD71 /* Build configuration list for PBXNativeTarget "FLBugKitDemo" */; 148 | buildPhases = ( 149 | FFFF4588151D019D007ACD71 /* Sources */, 150 | FFFF4589151D019D007ACD71 /* Frameworks */, 151 | FFFF458A151D019D007ACD71 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = FLBugKitDemo; 158 | productName = FLBugKitDemo; 159 | productReference = FFFF458C151D019D007ACD71 /* FLBugKitDemo.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | FFFF4583151D019D007ACD71 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | CLASSPREFIX = FL; 169 | LastUpgradeCheck = 0430; 170 | ORGANIZATIONNAME = Evan; 171 | }; 172 | buildConfigurationList = FFFF4586151D019D007ACD71 /* Build configuration list for PBXProject "FLBugKitDemo" */; 173 | compatibilityVersion = "Xcode 3.2"; 174 | developmentRegion = English; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | ); 179 | mainGroup = FFFF4581151D019D007ACD71; 180 | productRefGroup = FFFF458D151D019D007ACD71 /* Products */; 181 | projectDirPath = ""; 182 | projectRoot = ""; 183 | targets = ( 184 | FFFF458B151D019D007ACD71 /* FLBugKitDemo */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | FFFF458A151D019D007ACD71 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | FFFF459B151D019D007ACD71 /* InfoPlist.strings in Resources */, 195 | FFFF45AA151D019D007ACD71 /* FLMasterViewController_iPhone.xib in Resources */, 196 | FFFF45AD151D019D007ACD71 /* FLMasterViewController_iPad.xib in Resources */, 197 | FFFF45B0151D019D007ACD71 /* FLDetailViewController_iPhone.xib in Resources */, 198 | FFFF45B3151D019D007ACD71 /* FLDetailViewController_iPad.xib in Resources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXResourcesBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | FFFF4588151D019D007ACD71 /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | FFFF459D151D019D007ACD71 /* main.m in Sources */, 210 | FFFF45A1151D019D007ACD71 /* FLAppDelegate.m in Sources */, 211 | FFFF45A4151D019D007ACD71 /* FLMasterViewController.m in Sources */, 212 | FFFF45A7151D019D007ACD71 /* FLDetailViewController.m in Sources */, 213 | FFFF45C0151D0C87007ACD71 /* FLBugKit.m in Sources */, 214 | FFFF45C1151D0C87007ACD71 /* UISplitViewController+FLBugKit.m in Sources */, 215 | FFFF45C2151D0C87007ACD71 /* UIViewController+FLBugKit.m in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXSourcesBuildPhase section */ 220 | 221 | /* Begin PBXVariantGroup section */ 222 | FFFF4599151D019D007ACD71 /* InfoPlist.strings */ = { 223 | isa = PBXVariantGroup; 224 | children = ( 225 | FFFF459A151D019D007ACD71 /* en */, 226 | ); 227 | name = InfoPlist.strings; 228 | sourceTree = ""; 229 | }; 230 | FFFF45A8151D019D007ACD71 /* FLMasterViewController_iPhone.xib */ = { 231 | isa = PBXVariantGroup; 232 | children = ( 233 | FFFF45A9151D019D007ACD71 /* en */, 234 | ); 235 | name = FLMasterViewController_iPhone.xib; 236 | sourceTree = ""; 237 | }; 238 | FFFF45AB151D019D007ACD71 /* FLMasterViewController_iPad.xib */ = { 239 | isa = PBXVariantGroup; 240 | children = ( 241 | FFFF45AC151D019D007ACD71 /* en */, 242 | ); 243 | name = FLMasterViewController_iPad.xib; 244 | sourceTree = ""; 245 | }; 246 | FFFF45AE151D019D007ACD71 /* FLDetailViewController_iPhone.xib */ = { 247 | isa = PBXVariantGroup; 248 | children = ( 249 | FFFF45AF151D019D007ACD71 /* en */, 250 | ); 251 | name = FLDetailViewController_iPhone.xib; 252 | sourceTree = ""; 253 | }; 254 | FFFF45B1151D019D007ACD71 /* FLDetailViewController_iPad.xib */ = { 255 | isa = PBXVariantGroup; 256 | children = ( 257 | FFFF45B2151D019D007ACD71 /* en */, 258 | ); 259 | name = FLDetailViewController_iPad.xib; 260 | sourceTree = ""; 261 | }; 262 | /* End PBXVariantGroup section */ 263 | 264 | /* Begin XCBuildConfiguration section */ 265 | FFFF45B4151D019D007ACD71 /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 270 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 271 | COPY_PHASE_STRIP = NO; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_OPTIMIZATION_LEVEL = 0; 275 | GCC_PREPROCESSOR_DEFINITIONS = ( 276 | "DEBUG=1", 277 | "$(inherited)", 278 | ); 279 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 280 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 285 | SDKROOT = iphoneos; 286 | TARGETED_DEVICE_FAMILY = "1,2"; 287 | }; 288 | name = Debug; 289 | }; 290 | FFFF45B5151D019D007ACD71 /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ALWAYS_SEARCH_USER_PATHS = NO; 294 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 296 | COPY_PHASE_STRIP = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 299 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 303 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 304 | SDKROOT = iphoneos; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | VALIDATE_PRODUCT = YES; 307 | }; 308 | name = Release; 309 | }; 310 | FFFF45B7151D019D007ACD71 /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 314 | GCC_PREFIX_HEADER = "FLBugKitDemo/FLBugKitDemo-Prefix.pch"; 315 | INFOPLIST_FILE = "FLBugKitDemo/FLBugKitDemo-Info.plist"; 316 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | WRAPPER_EXTENSION = app; 319 | }; 320 | name = Debug; 321 | }; 322 | FFFF45B8151D019D007ACD71 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 326 | GCC_PREFIX_HEADER = "FLBugKitDemo/FLBugKitDemo-Prefix.pch"; 327 | INFOPLIST_FILE = "FLBugKitDemo/FLBugKitDemo-Info.plist"; 328 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | WRAPPER_EXTENSION = app; 331 | }; 332 | name = Release; 333 | }; 334 | /* End XCBuildConfiguration section */ 335 | 336 | /* Begin XCConfigurationList section */ 337 | FFFF4586151D019D007ACD71 /* Build configuration list for PBXProject "FLBugKitDemo" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | FFFF45B4151D019D007ACD71 /* Debug */, 341 | FFFF45B5151D019D007ACD71 /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | FFFF45B6151D019D007ACD71 /* Build configuration list for PBXNativeTarget "FLBugKitDemo" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | FFFF45B7151D019D007ACD71 /* Debug */, 350 | FFFF45B8151D019D007ACD71 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | }; 354 | /* End XCConfigurationList section */ 355 | }; 356 | rootObject = FFFF4583151D019D007ACD71 /* Project object */; 357 | } 358 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLAppDelegate.h 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "FLBugKit.h" 12 | #import "UIViewController+FLBugKit.h" 13 | #import "UISplitViewController+FLBugKit.h" 14 | 15 | @interface FLAppDelegate : UIResponder 16 | 17 | @property (strong, nonatomic) UIWindow *window; 18 | 19 | @property (strong, nonatomic) UINavigationController *navigationController; 20 | 21 | @property (strong, nonatomic) UISplitViewController *splitViewController; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLAppDelegate.m 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import "FLAppDelegate.h" 10 | #import "FLMasterViewController.h" 11 | #import "FLDetailViewController.h" 12 | 13 | @implementation FLAppDelegate 14 | 15 | #pragma mark - FLBugKit 16 | 17 | - (NSString *)userId { 18 | return @"sample user id"; 19 | } 20 | 21 | - (NSString *)defaultBugMetadata { 22 | return @"Information besides screenshot and version information"; 23 | } 24 | 25 | - (NSString *)defaultBugEmailAddress { 26 | return @"mybugemail@myproject.fogbugz.com"; 27 | } 28 | 29 | - (NSString *)defaultBugEmailSubject { 30 | return @"Subject of the bug email"; 31 | } 32 | 33 | - (UIGestureRecognizer *)bugGestureRecognizer { 34 | UILongPressGestureRecognizer *bugGesture = [[[UILongPressGestureRecognizer alloc] init] autorelease]; 35 | bugGesture.minimumPressDuration = 0.7; 36 | bugGesture.numberOfTouchesRequired = 2; 37 | return bugGesture; 38 | } 39 | 40 | - (UIViewController *)activeViewControllerFromRootViewController:(UIViewController *)rootViewController gesture:(UIGestureRecognizer *)gesture { 41 | if ([rootViewController isKindOfClass:[UISplitViewController class]]) { 42 | UISplitViewController *split = (UISplitViewController *)rootViewController; 43 | rootViewController = [split viewControllerTargetedByGesture:gesture]; 44 | } 45 | return [rootViewController findTopMostViewController]; 46 | } 47 | 48 | 49 | #pragma mark - Generated Code 50 | 51 | @synthesize window = _window; 52 | @synthesize navigationController = _navigationController; 53 | @synthesize splitViewController = _splitViewController; 54 | 55 | - (void)dealloc 56 | { 57 | [_window release]; 58 | [_navigationController release]; 59 | [_splitViewController release]; 60 | [super dealloc]; 61 | } 62 | 63 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 64 | { 65 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 66 | // Override point for customization after application launch. 67 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 68 | FLMasterViewController *masterViewController = [[[FLMasterViewController alloc] initWithNibName:@"FLMasterViewController_iPhone" bundle:nil] autorelease]; 69 | self.navigationController = [[[UINavigationController alloc] initWithRootViewController:masterViewController] autorelease]; 70 | self.window.rootViewController = self.navigationController; 71 | } else { 72 | FLMasterViewController *masterViewController = [[[FLMasterViewController alloc] initWithNibName:@"FLMasterViewController_iPad" bundle:nil] autorelease]; 73 | UINavigationController *masterNavigationController = [[[UINavigationController alloc] initWithRootViewController:masterViewController] autorelease]; 74 | 75 | FLDetailViewController *detailViewController = [[[FLDetailViewController alloc] initWithNibName:@"FLDetailViewController_iPad" bundle:nil] autorelease]; 76 | UINavigationController *detailNavigationController = [[[UINavigationController alloc] initWithRootViewController:detailViewController] autorelease]; 77 | 78 | masterViewController.detailViewController = detailViewController; 79 | 80 | self.splitViewController = [[[UISplitViewController alloc] init] autorelease]; 81 | self.splitViewController.delegate = detailViewController; 82 | self.splitViewController.viewControllers = [NSArray arrayWithObjects:masterNavigationController, detailNavigationController, nil]; 83 | 84 | self.window.rootViewController = self.splitViewController; 85 | } 86 | [[FLBugKit sharedInstance] startMonitoringWindow:self.window]; 87 | [self.window makeKeyAndVisible]; 88 | return YES; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLBugKitDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.scribd.demo.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLBugKitDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'FLBugKitDemo' target in the 'FLBugKitDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLDetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLDetailViewController.h 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FLBugKit.h" 11 | 12 | @interface FLDetailViewController : UIViewController 13 | 14 | @property (strong, nonatomic) id detailItem; 15 | 16 | @property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLDetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLDetailViewController.m 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import "FLDetailViewController.h" 10 | 11 | @interface FLDetailViewController () 12 | @property (strong, nonatomic) UIPopoverController *masterPopoverController; 13 | - (void)configureView; 14 | @end 15 | 16 | @implementation FLDetailViewController 17 | 18 | #pragma mark - FLBugKit 19 | 20 | - (NSString *)specificBugEmailSubject { 21 | return @"FLDetailViewController Subject"; 22 | } 23 | 24 | #pragma mark - Generated Code 25 | 26 | @synthesize detailItem = _detailItem; 27 | @synthesize detailDescriptionLabel = _detailDescriptionLabel; 28 | @synthesize masterPopoverController = _masterPopoverController; 29 | 30 | - (void)dealloc 31 | { 32 | [_detailItem release]; 33 | [_detailDescriptionLabel release]; 34 | [_masterPopoverController release]; 35 | [super dealloc]; 36 | } 37 | 38 | #pragma mark - Managing the detail item 39 | 40 | - (void)setDetailItem:(id)newDetailItem 41 | { 42 | if (_detailItem != newDetailItem) { 43 | [_detailItem release]; 44 | _detailItem = [newDetailItem retain]; 45 | 46 | // Update the view. 47 | [self configureView]; 48 | } 49 | 50 | if (self.masterPopoverController != nil) { 51 | [self.masterPopoverController dismissPopoverAnimated:YES]; 52 | } 53 | } 54 | 55 | - (void)configureView 56 | { 57 | // Update the user interface for the detail item. 58 | 59 | if (self.detailItem) { 60 | self.detailDescriptionLabel.text = [self.detailItem description]; 61 | } 62 | } 63 | 64 | - (void)viewDidLoad 65 | { 66 | [super viewDidLoad]; 67 | // Do any additional setup after loading the view, typically from a nib. 68 | [self configureView]; 69 | } 70 | 71 | - (void)viewDidUnload 72 | { 73 | [super viewDidUnload]; 74 | // Release any retained subviews of the main view. 75 | self.detailDescriptionLabel = nil; 76 | } 77 | 78 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 79 | { 80 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 81 | return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); 82 | } else { 83 | return YES; 84 | } 85 | } 86 | 87 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 88 | { 89 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 90 | if (self) { 91 | self.title = NSLocalizedString(@"Detail", @"Detail"); 92 | } 93 | return self; 94 | } 95 | 96 | #pragma mark - Split view 97 | 98 | - (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController 99 | { 100 | barButtonItem.title = NSLocalizedString(@"Master", @"Master"); 101 | [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES]; 102 | self.masterPopoverController = popoverController; 103 | } 104 | 105 | - (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem 106 | { 107 | // Called when the view is shown again in the split view, invalidating the button and popover controller. 108 | [self.navigationItem setLeftBarButtonItem:nil animated:YES]; 109 | self.masterPopoverController = nil; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLMasterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLMasterViewController.h 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FLBugKit.h" 11 | 12 | @class FLDetailViewController; 13 | 14 | @interface FLMasterViewController : UITableViewController 15 | 16 | @property (strong, nonatomic) FLDetailViewController *detailViewController; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/FLMasterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLMasterViewController.m 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import "FLMasterViewController.h" 10 | 11 | #import "FLDetailViewController.h" 12 | 13 | @interface FLMasterViewController () { 14 | NSMutableArray *_objects; 15 | } 16 | @end 17 | 18 | @implementation FLMasterViewController 19 | 20 | #pragma mark - FLBugKit 21 | 22 | - (NSString *)specificBugEmailSubject { 23 | return @"FLMasterViewController Subject"; 24 | } 25 | 26 | #pragma mark - Generated Code 27 | 28 | @synthesize detailViewController = _detailViewController; 29 | 30 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 31 | { 32 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 33 | if (self) { 34 | self.title = NSLocalizedString(@"Master", @"Master"); 35 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 36 | self.clearsSelectionOnViewWillAppear = NO; 37 | self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0); 38 | } 39 | } 40 | return self; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | [_detailViewController release]; 46 | [_objects release]; 47 | [super dealloc]; 48 | } 49 | 50 | - (void)viewDidLoad 51 | { 52 | [super viewDidLoad]; 53 | // Do any additional setup after loading the view, typically from a nib. 54 | self.navigationItem.leftBarButtonItem = self.editButtonItem; 55 | 56 | UIBarButtonItem *addButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)] autorelease]; 57 | self.navigationItem.rightBarButtonItem = addButton; 58 | } 59 | 60 | - (void)viewDidUnload 61 | { 62 | [super viewDidUnload]; 63 | // Release any retained subviews of the main view. 64 | } 65 | 66 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 67 | { 68 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 69 | return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); 70 | } else { 71 | return YES; 72 | } 73 | } 74 | 75 | - (void)insertNewObject:(id)sender 76 | { 77 | if (!_objects) { 78 | _objects = [[NSMutableArray alloc] init]; 79 | } 80 | [_objects insertObject:[NSDate date] atIndex:0]; 81 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; 82 | [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 83 | } 84 | 85 | #pragma mark - Table View 86 | 87 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 88 | { 89 | return 1; 90 | } 91 | 92 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 93 | { 94 | return _objects.count; 95 | } 96 | 97 | // Customize the appearance of table view cells. 98 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 99 | { 100 | static NSString *CellIdentifier = @"Cell"; 101 | 102 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 103 | if (cell == nil) { 104 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 105 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 106 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 107 | } 108 | } 109 | 110 | 111 | NSDate *object = [_objects objectAtIndex:indexPath.row]; 112 | cell.textLabel.text = [object description]; 113 | return cell; 114 | } 115 | 116 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 117 | { 118 | // Return NO if you do not want the specified item to be editable. 119 | return YES; 120 | } 121 | 122 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 123 | { 124 | if (editingStyle == UITableViewCellEditingStyleDelete) { 125 | [_objects removeObjectAtIndex:indexPath.row]; 126 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 127 | } else if (editingStyle == UITableViewCellEditingStyleInsert) { 128 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 129 | } 130 | } 131 | 132 | /* 133 | // Override to support rearranging the table view. 134 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 135 | { 136 | } 137 | */ 138 | 139 | /* 140 | // Override to support conditional rearranging of the table view. 141 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 142 | { 143 | // Return NO if you do not want the item to be re-orderable. 144 | return YES; 145 | } 146 | */ 147 | 148 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 149 | { 150 | NSDate *object = [_objects objectAtIndex:indexPath.row]; 151 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 152 | if (!self.detailViewController) { 153 | self.detailViewController = [[[FLDetailViewController alloc] initWithNibName:@"FLDetailViewController_iPhone" bundle:nil] autorelease]; 154 | } 155 | self.detailViewController.detailItem = object; 156 | [self.navigationController pushViewController:self.detailViewController animated:YES]; 157 | } else { 158 | self.detailViewController.detailItem = object; 159 | } 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/en.lproj/FLDetailViewController_iPad.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C25 6 | 1919 7 | 1138.11 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 916 12 | 13 | 14 | IBProxyObject 15 | IBUIView 16 | IBUILabel 17 | 18 | 19 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 20 | 21 | 22 | PluginDependencyRecalculationVersion 23 | 24 | 25 | 26 | 27 | IBFilesOwner 28 | IBIPadFramework 29 | 30 | 31 | IBFirstResponder 32 | IBIPadFramework 33 | 34 | 35 | 36 | 274 37 | 38 | 39 | 40 | 298 41 | {{20, 491}, {728, 21}} 42 | 43 | 44 | 45 | 3 46 | MQA 47 | 48 | YES 49 | NO 50 | IBIPadFramework 51 | Detail view content goes here 52 | 53 | 1 54 | MCAwIDAAA 55 | 56 | 57 | 1 58 | 10 59 | 1 60 | 61 | 1 62 | 4 63 | 64 | 65 | Helvetica 66 | 14 67 | 16 68 | 69 | 70 | 71 | {{0, 20}, {768, 1004}} 72 | 73 | 74 | 75 | NO 76 | 77 | 2 78 | 79 | IBIPadFramework 80 | 81 | 82 | 83 | 84 | 85 | 86 | view 87 | 88 | 89 | 90 | 12 91 | 92 | 93 | 94 | detailDescriptionLabel 95 | 96 | 97 | 98 | 66 99 | 100 | 101 | 102 | 103 | 104 | 0 105 | 106 | 107 | 108 | 109 | 110 | -1 111 | 112 | 113 | File's Owner 114 | 115 | 116 | -2 117 | 118 | 119 | 120 | 121 | 8 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 45 130 | 131 | 132 | 133 | 134 | 135 | 136 | FLDetailViewController 137 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 138 | UIResponder 139 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 140 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 141 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 142 | 143 | 144 | 145 | 146 | 147 | 66 148 | 149 | 150 | 151 | 152 | FLDetailViewController 153 | UIViewController 154 | 155 | UILabel 156 | id 157 | UIToolbar 158 | 159 | 160 | 161 | detailDescriptionLabel 162 | UILabel 163 | 164 | 165 | detailItem 166 | id 167 | 168 | 169 | toolbar 170 | UIToolbar 171 | 172 | 173 | 174 | IBProjectSource 175 | ./Classes/FLDetailViewController.h 176 | 177 | 178 | 179 | 180 | 0 181 | IBIPadFramework 182 | YES 183 | 3 184 | 916 185 | 186 | 187 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/en.lproj/FLDetailViewController_iPhone.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C25 6 | 1919 7 | 1138.11 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 916 12 | 13 | 14 | IBProxyObject 15 | IBUIView 16 | IBUILabel 17 | 18 | 19 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 20 | 21 | 22 | PluginDependencyRecalculationVersion 23 | 24 | 25 | 26 | 27 | IBFilesOwner 28 | IBCocoaTouchFramework 29 | 30 | 31 | IBFirstResponder 32 | IBCocoaTouchFramework 33 | 34 | 35 | 36 | 274 37 | 38 | 39 | 40 | 298 41 | {{20, 221}, {280, 18}} 42 | 43 | 44 | 45 | 3 46 | MQA 47 | 48 | YES 49 | NO 50 | IBCocoaTouchFramework 51 | Detail view content goes here 52 | 53 | 1 54 | MCAwIDAAA 55 | 56 | 57 | 1 58 | 10 59 | 1 60 | 61 | 1 62 | 4 63 | 64 | 65 | Helvetica 66 | 14 67 | 16 68 | 69 | 70 | 71 | {{0, 20}, {320, 460}} 72 | 73 | 74 | 75 | 3 76 | MQA 77 | 78 | 2 79 | 80 | 81 | 82 | IBCocoaTouchFramework 83 | 84 | 85 | 86 | 87 | 88 | 89 | view 90 | 91 | 92 | 93 | 3 94 | 95 | 96 | 97 | detailDescriptionLabel 98 | 99 | 100 | 101 | 6 102 | 103 | 104 | 105 | 106 | 107 | 0 108 | 109 | 110 | 111 | 112 | 113 | 1 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -1 122 | 123 | 124 | File's Owner 125 | 126 | 127 | -2 128 | 129 | 130 | 131 | 132 | 4 133 | 134 | 135 | 136 | 137 | 138 | 139 | FLDetailViewController 140 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 141 | UIResponder 142 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 143 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 144 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 145 | 146 | 147 | 148 | 149 | 150 | 6 151 | 152 | 153 | 154 | 155 | FLDetailViewController 156 | UIViewController 157 | 158 | detailDescriptionLabel 159 | UILabel 160 | 161 | 162 | detailDescriptionLabel 163 | 164 | detailDescriptionLabel 165 | UILabel 166 | 167 | 168 | 169 | IBProjectSource 170 | ./Classes/FLDetailViewController.h 171 | 172 | 173 | 174 | 175 | 0 176 | IBCocoaTouchFramework 177 | YES 178 | 3 179 | 916 180 | 181 | 182 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/en.lproj/FLMasterViewController_iPad.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C25 6 | 1919 7 | 1138.11 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 916 12 | 13 | 14 | IBProxyObject 15 | IBUITableView 16 | 17 | 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | PluginDependencyRecalculationVersion 22 | 23 | 24 | 25 | 26 | IBFilesOwner 27 | IBIPadFramework 28 | 29 | 30 | IBFirstResponder 31 | IBIPadFramework 32 | 33 | 34 | 35 | 274 36 | {{0, 20}, {320, 832}} 37 | 38 | 39 | 40 | 3 41 | MQA 42 | 43 | YES 44 | 45 | 2 46 | 47 | 48 | IBUISplitViewMasterSimulatedSizeMetrics 49 | 50 | YES 51 | 52 | 53 | 54 | 55 | 56 | {320, 852} 57 | {320, 768} 58 | 59 | 60 | IBIPadFramework 61 | Master 62 | IBUISplitViewController 63 | 64 | IBUISplitViewControllerContentSizeLocation 65 | IBUISplitViewControllerContentSizeLocationMaster 66 | 67 | 68 | IBIPadFramework 69 | YES 70 | 1 71 | 0 72 | YES 73 | 44 74 | 22 75 | 22 76 | 77 | 78 | 79 | 80 | 81 | 82 | view 83 | 84 | 85 | 86 | 3 87 | 88 | 89 | 90 | dataSource 91 | 92 | 93 | 94 | 4 95 | 96 | 97 | 98 | delegate 99 | 100 | 101 | 102 | 5 103 | 104 | 105 | 106 | 107 | 108 | 0 109 | 110 | 111 | 112 | 113 | 114 | -1 115 | 116 | 117 | File's Owner 118 | 119 | 120 | -2 121 | 122 | 123 | 124 | 125 | 2 126 | 127 | 128 | 129 | 130 | 131 | 132 | FLMasterViewController 133 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 134 | UIResponder 135 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 136 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 137 | 138 | 139 | 140 | 141 | 142 | 5 143 | 144 | 145 | 146 | 147 | FLMasterViewController 148 | UITableViewController 149 | 150 | IBProjectSource 151 | ./Classes/FLMasterViewController.h 152 | 153 | 154 | 155 | 156 | 0 157 | IBIPadFramework 158 | YES 159 | 3 160 | 916 161 | 162 | 163 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/en.lproj/FLMasterViewController_iPhone.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C25 6 | 1919 7 | 1138.11 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 916 12 | 13 | 14 | IBProxyObject 15 | IBUITableView 16 | 17 | 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | PluginDependencyRecalculationVersion 22 | 23 | 24 | 25 | 26 | IBFilesOwner 27 | IBCocoaTouchFramework 28 | 29 | 30 | IBFirstResponder 31 | IBCocoaTouchFramework 32 | 33 | 34 | 35 | 274 36 | {{0, 20}, {320, 460}} 37 | 38 | 39 | 40 | 3 41 | MQA 42 | 43 | YES 44 | 45 | IBCocoaTouchFramework 46 | YES 47 | 1 48 | 0 49 | YES 50 | 44 51 | 22 52 | 22 53 | 54 | 55 | 56 | 57 | 58 | 59 | view 60 | 61 | 62 | 63 | 3 64 | 65 | 66 | 67 | dataSource 68 | 69 | 70 | 71 | 4 72 | 73 | 74 | 75 | delegate 76 | 77 | 78 | 79 | 5 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | -1 92 | 93 | 94 | File's Owner 95 | 96 | 97 | -2 98 | 99 | 100 | 101 | 102 | 2 103 | 104 | 105 | 106 | 107 | 108 | 109 | FLMasterViewController 110 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 111 | UIResponder 112 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 113 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 114 | 115 | 116 | 117 | 118 | 119 | 5 120 | 121 | 122 | 123 | 124 | FLMasterViewController 125 | UITableViewController 126 | 127 | IBProjectSource 128 | ./Classes/FLMasterViewController.h 129 | 130 | 131 | 132 | 133 | 0 134 | IBCocoaTouchFramework 135 | YES 136 | 3 137 | 916 138 | 139 | 140 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /demo/FLBugKitDemo/FLBugKitDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FLBugKitDemo 4 | // 5 | // Created by Evan Long on 3/23/12. 6 | // Copyright (c) 2012 Evan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "FLAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([FLAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gendocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | appledoc --company-id com.scribd\ 4 | --project-company=Scribd\ 5 | --project-name=FLBugKit\ 6 | --no-create-docset\ 7 | -o docs/\ 8 | src/ -------------------------------------------------------------------------------- /src/FLBugKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLBugKit.h 3 | // Float 4 | // 5 | // Created by Evan Long on 2/4/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | Protocol must be implemented by the `UIApplicationDelegate` 14 | */ 15 | @protocol FLStandardApplicationBugMetadataProtocol 16 | 17 | /** 18 | Returns a string that can be used to identify a user for the 19 | application. 20 | 21 | @return string that identifies the user 22 | */ 23 | - (NSString *)userId; 24 | 25 | /** 26 | This string of application bug metadata in addition to a screenshot, 27 | version number and build number. 28 | 29 | @return string of metadata about the application 30 | */ 31 | - (NSString *)defaultBugMetadata; 32 | 33 | /** 34 | Providing a custom email can be useful in directing bugs to the 35 | right person or team. 36 | 37 | @return default email address that the bug will be sent to 38 | */ 39 | - (NSString *)defaultBugEmailAddress; 40 | 41 | /** 42 | Providing a subject can be useful in categorizing bugs within your 43 | bug tracking system. 44 | 45 | @return default subject of the bug email 46 | */ 47 | - (NSString *)defaultBugEmailSubject; 48 | 49 | /** 50 | Provides the `UIGestureRecognizer` that when triggered will cause 51 | the collection of data and prompt the user to email this data to 52 | the developers. `FLBugKit` instance will add itself as a target to 53 | handle the gesture event. 54 | 55 | @return `UIGestureRecognizer` registered with `UIWindow` instances 56 | */ 57 | - (UIGestureRecognizer *)bugGestureRecognizer; 58 | 59 | /** 60 | Finds the active view controller given a window's root view controller. The 61 | result will be asked for additional metadata for the bug if it is available. 62 | 63 | @param rootViewController The rootViewController from the `UIWindow` that 64 | handled the gesture. 65 | 66 | @param gesture The gesture that triggered the bug metadata to be collected 67 | 68 | @return The view controller that may have additional bug metadata 69 | 70 | @discussion It can be useful for the case where the root view controller for 71 | a window is a controller of other controllers such as a `UINavigationController`. 72 | */ 73 | - (UIViewController *)activeViewControllerFromRootViewController:(UIViewController *)rootViewController gesture:(UIGestureRecognizer *)gesture; 74 | 75 | @end 76 | 77 | /** 78 | Protocol that is implemented by a `UIViewController` when additional 79 | metadata is required reguarding the view the user is interacting with. 80 | */ 81 | @protocol FLAdditionalViewControllerBugMetadataProtocol 82 | 83 | @optional 84 | /** 85 | Provides string of metadata about the active view controller. The information 86 | returned is appended in addition to the `defaultBugMetadata` provided by the 87 | `FLStandardApplicationBugMetadataProtocol`. 88 | 89 | @return string of metadata about the active view controller 90 | */ 91 | - (NSString *)additionalBugMetadata; 92 | 93 | /** 94 | Overrides the email address provided by the `FLStandardApplicationBugMetadataProtocol` 95 | 96 | @return email address the bug will be sent to 97 | */ 98 | - (NSString *)specificBugEmailAddress; 99 | 100 | /** 101 | Overrides the email subject provided by the `FLStandardApplicationBugMetadataProtocol` 102 | 103 | @return subject of the bug email 104 | */ 105 | - (NSString *)specificBugEmailSubject; 106 | 107 | @end 108 | 109 | /** 110 | `FLBugKit` is a utility for filing bugs via email from within an 111 | application. 112 | 113 | It is required that the `UIApplicationDelegate` implement the 114 | `FLStandardApplicationBugMetadataProtocol`. 115 | 116 | When an email for a bug is created a screenshot of the window will be 117 | taken, the `FLStandardApplicationBugMetadataProtocol` methods will be called to 118 | gather the bug's metadata, and if the active view controller implements 119 | the `FLAdditionalViewControllerBugMetadataProtocol` its data will be included as well. 120 | 121 | `FLBugKit` will always include a screenshot, version number (CFBundleShortVersionString), 122 | and build number (CFBundleVersion) in addition to the data returned by 123 | `FLStandardApplicationBugMetadataProtocol` and `FLAdditionalViewControllerBugMetadataProtocol`. 124 | */ 125 | @interface FLBugKit : NSObject 126 | 127 | /** 128 | Creates and returns the `sharedInstance` of `FLBugKit`. If it already 129 | exists it simply returns it. 130 | 131 | @return the application's shared instance of `FLBugKit` 132 | */ 133 | + (FLBugKit *)sharedInstance; 134 | 135 | /** 136 | Starts monitoring a window for a bug gesture event using the 137 | `UIGestureRecognizer` instance provided by the implementation of 138 | the `FLStandardApplicationBugMetadataProtocol` by the `sharedApplication` 139 | delegate. 140 | 141 | @param window The window instance that will be monitored 142 | */ 143 | - (void)startMonitoringWindow:(UIWindow *)window; 144 | 145 | /** 146 | Removes the `UIGestureRecognizer` from the `UIWindow` that was attached 147 | with `startMonitoringWindow:`. 148 | 149 | @param window The window that will stopped being monitored 150 | */ 151 | - (void)stopMonitoringWindow:(UIWindow *)window; 152 | 153 | /** 154 | Programatically present the bug mailer without waiting for a `UIWindow` 155 | gesture recognizer to be raised. The `keyWindow` for the `sharedApplication` 156 | will be used to retrieve 157 | */ 158 | - (void)presentBugMailerForKeyWindow; 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /src/FLBugKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLBugKit.m 3 | // Float 4 | // 5 | // Created by Evan Long on 2/4/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FLBugKit.h" 11 | 12 | @interface FLBugKit () { 13 | NSMutableDictionary *_windowToGesture; 14 | } 15 | 16 | - (void)_handleBugGesture:(UIGestureRecognizer *)gesture; 17 | - (void)_showMailControllerForWindow:(UIWindow *)window gesture:(UIGestureRecognizer *)gesture; 18 | - (NSData *)_captureScreenShotForView:(UIView *)view; 19 | 20 | @property (nonatomic, readonly) id standardBugDataSource; 21 | 22 | @end 23 | 24 | @implementation FLBugKit 25 | 26 | #pragma mark - NSObject 27 | 28 | - (void)dealloc { 29 | [_windowToGesture release]; 30 | [super dealloc]; 31 | } 32 | 33 | - (id)init { 34 | if ((self = [super init])) { 35 | _windowToGesture = [[NSMutableDictionary alloc] init]; 36 | } 37 | return self; 38 | } 39 | 40 | #pragma mark - FLBugKit 41 | 42 | + (FLBugKit *)sharedInstance { 43 | static dispatch_once_t onceToken; 44 | static FLBugKit *_sharedInstance; 45 | dispatch_once(&onceToken, ^{ 46 | _sharedInstance = [[FLBugKit alloc] init]; 47 | }); 48 | 49 | return _sharedInstance; 50 | } 51 | 52 | - (id )standardBugDataSource { 53 | id appDelegate = [UIApplication sharedApplication].delegate; 54 | if ([appDelegate conformsToProtocol:@protocol(FLStandardApplicationBugMetadataProtocol)]) { 55 | return appDelegate; 56 | } 57 | 58 | return nil; 59 | } 60 | 61 | - (void)startMonitoringWindow:(UIWindow *)window { 62 | if ([_windowToGesture objectForKey:window] == nil) { 63 | UIGestureRecognizer *gesture = [self.standardBugDataSource bugGestureRecognizer]; 64 | [gesture addTarget:self action:@selector(_handleBugGesture:)]; 65 | [window addGestureRecognizer:gesture]; 66 | [_windowToGesture setObject:gesture forKey:[NSValue valueWithNonretainedObject:window]]; 67 | } 68 | } 69 | 70 | - (void)stopMonitoringWindow:(UIWindow *)window { 71 | NSValue *windowValue = [NSValue valueWithNonretainedObject:window]; 72 | [window removeGestureRecognizer:[_windowToGesture objectForKey:windowValue]]; 73 | [_windowToGesture removeObjectForKey:windowValue]; 74 | } 75 | 76 | - (void)presentBugMailerForKeyWindow { 77 | [self _showMailControllerForWindow:[UIApplication sharedApplication].keyWindow gesture:nil]; 78 | } 79 | 80 | #pragma mark - Private 81 | 82 | - (void)_handleBugGesture:(UIGestureRecognizer *)gesture { 83 | if (gesture.state == UIGestureRecognizerStateEnded) { 84 | UIWindow *window = (UIWindow *)gesture.view; 85 | if ([window isKindOfClass:[UIWindow class]] == NO) { 86 | return; 87 | } 88 | 89 | [self _showMailControllerForWindow:window gesture:gesture]; 90 | } 91 | } 92 | 93 | - (void)_showMailControllerForWindow:(UIWindow *)window gesture:(UIGestureRecognizer *)gesture { 94 | if([MFMailComposeViewController canSendMail]) { 95 | UIViewController *activeViewController = [self.standardBugDataSource activeViewControllerFromRootViewController:window.rootViewController gesture:gesture]; 96 | if ([activeViewController conformsToProtocol:@protocol(FLAdditionalViewControllerBugMetadataProtocol)] == NO) { 97 | if ([window.rootViewController conformsToProtocol:@protocol(FLAdditionalViewControllerBugMetadataProtocol)]) { 98 | activeViewController = window.rootViewController; 99 | } 100 | else { 101 | activeViewController = nil; 102 | } 103 | } 104 | 105 | // Start bug metadata collection 106 | NSString *userId = [self.standardBugDataSource userId]; 107 | NSString *bundleVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; 108 | NSString *versionShortString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 109 | NSString *bugMetadata = [NSString stringWithFormat:@"userId=%@\nbundle version=%@\nversion short string=%@\n\n%@\n\n", userId, bundleVersion, versionShortString, [self.standardBugDataSource defaultBugMetadata]]; 110 | NSString *emailAddress = nil; 111 | NSString *emailSubject = nil; 112 | 113 | id additionalBugDataSource = (id )activeViewController; 114 | if ([additionalBugDataSource respondsToSelector:@selector(additionalBugMetadata)]) { 115 | bugMetadata = [bugMetadata stringByAppendingFormat:@"%@", [additionalBugDataSource additionalBugMetadata]]; 116 | } 117 | 118 | if ([additionalBugDataSource respondsToSelector:@selector(specificBugEmailAddress)]) { 119 | emailAddress = [additionalBugDataSource specificBugEmailAddress]; 120 | } 121 | else { 122 | emailAddress = [self.standardBugDataSource defaultBugEmailAddress]; 123 | } 124 | 125 | if ([additionalBugDataSource respondsToSelector:@selector(specificBugEmailSubject)]) { 126 | emailSubject = [additionalBugDataSource specificBugEmailSubject]; 127 | } 128 | else { 129 | emailSubject = [self.standardBugDataSource defaultBugEmailSubject]; 130 | } 131 | 132 | MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init]; 133 | mailViewController.mailComposeDelegate = self; 134 | [mailViewController setToRecipients:[NSArray arrayWithObject:emailAddress]]; 135 | [mailViewController setSubject:emailSubject]; 136 | [mailViewController setMessageBody:@"Please include any addition feedback for the developers to address your issue." isHTML:NO]; 137 | [mailViewController addAttachmentData:[bugMetadata dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain;charset=utf-8" fileName:@"bugdata.txt"]; 138 | [mailViewController addAttachmentData:[self _captureScreenShotForView:window] mimeType:@"image/png" fileName:@"screenshot.png"]; 139 | [window.rootViewController presentModalViewController:mailViewController animated:YES]; 140 | [mailViewController release]; 141 | } 142 | else { 143 | [[[[UIAlertView alloc] initWithTitle:@"Error" 144 | message:@"Please setup an email account on your device before trying to file a bug report." 145 | delegate:nil 146 | cancelButtonTitle:nil 147 | otherButtonTitles:@"Ok", nil] autorelease] show]; 148 | } 149 | } 150 | 151 | - (NSData *)_captureScreenShotForView:(UIView *)view { 152 | UIGraphicsBeginImageContext(view.bounds.size); 153 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 154 | NSData *pngData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()); 155 | UIGraphicsEndImageContext(); 156 | return pngData; 157 | } 158 | 159 | #pragma mark - MFMailComposeViewControllerDelegate 160 | 161 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 162 | [controller.view.window.rootViewController dismissModalViewControllerAnimated:YES]; 163 | } 164 | 165 | @end 166 | 167 | /* 168 | TODO: 169 | 2. email code (make sure email access exists). 170 | (better to provide source files alone with instructions to 171 | include mail framework or provide a xcodeproj/framework that 172 | includes that depedency. 173 | 3. Provide optional category to search through `UINavigationController`, 174 | `UISplitViewController`, `UITabbarController` for the active view controller 175 | 4. Add arbitrary NSData from the various protocols 176 | */ 177 | -------------------------------------------------------------------------------- /src/UISplitViewController+FLBugKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewController+FLBugKit.h 3 | // Float 4 | // 5 | // Created by Evan Long on 3/22/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UISplitViewController (FLBugKit) 12 | 13 | /** 14 | Helper to determine which child view controller the bug gesture is likely 15 | interacting with. 16 | 17 | @return view controller gesture is interacting with within a UISplitViewController 18 | */ 19 | - (UIViewController *)viewControllerTargetedByGesture:(UIGestureRecognizer *)gesture; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /src/UISplitViewController+FLBugKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewController+FLBugKit.m 3 | // Float 4 | // 5 | // Created by Evan Long on 3/22/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import "UISplitViewController+FLBugKit.h" 10 | 11 | @implementation UISplitViewController (FLBugKit) 12 | 13 | - (UIViewController *)viewControllerTargetedByGesture:(UIGestureRecognizer *)gesture { 14 | UIViewController *leftViewController = [self.viewControllers objectAtIndex:0]; 15 | UIViewController *rightViewController = [self.viewControllers objectAtIndex:1]; 16 | CGPoint p = [gesture locationInView:leftViewController.view]; 17 | 18 | if ([leftViewController.view pointInside:p withEvent:nil]) { 19 | return leftViewController; 20 | } 21 | else { 22 | return rightViewController; 23 | } 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /src/UIViewController+FLBugKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+FLBugKit.h 3 | // Float 4 | // 5 | // Created by Evan Long on 2/8/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | Utilities that are helpful for interacting with FLBugKit and standard UIViewControllers 13 | provided by UIKit. 14 | */ 15 | @interface UIViewController (FLBugKit) 16 | 17 | /** 18 | With a controller of controllers it is not always obvious which one the 19 | user is currently interacting with. `findTopMostViewController` is a 20 | simple search that looks for the top most view controller if the root 21 | is a `UINavigationController` or the active tab in the case of a 22 | `UISplitViewController`. 23 | 24 | @return view controller user is likely interacting with 25 | */ 26 | - (UIViewController *)findTopMostViewController; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /src/UIViewController+FLBugKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+FLBugKit.m 3 | // Float 4 | // 5 | // Created by Evan Long on 2/8/12. 6 | // Copyright (c) 2012 Scribd, Inc. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+FLBugKit.h" 10 | 11 | @implementation UIViewController (FLBugKit) 12 | 13 | - (UIViewController *)findTopMostViewController { 14 | UIViewController *result = self; 15 | if ([self isKindOfClass:[UINavigationController class]]) { 16 | UINavigationController *parent = (UINavigationController *)self; 17 | result = [parent.topViewController findTopMostViewController]; 18 | } else if ([self isKindOfClass:[UITabBarController class]]) { 19 | UITabBarController *parent = (UITabBarController *)self; 20 | result = [parent.selectedViewController findTopMostViewController]; 21 | } 22 | return result; 23 | } 24 | 25 | @end 26 | --------------------------------------------------------------------------------