├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── Jay.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── Jay.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE.md ├── README.md ├── Screenshots └── screenshot1.PNG ├── SwiftyFeedback.podspec ├── SwiftyFeedback.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── Jay.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── Jay.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── SwiftyFeedback ├── Info.plist ├── SwiftyFeedback.h ├── SwiftyFeedback.swift ├── SwiftyFeedbackAttachmentCell.swift ├── SwiftyFeedbackCell.swift ├── SwiftyFeedbackController.swift ├── SwiftyFeedbackEmail.swift └── SwiftyFeedbackExtensions.swift /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 693A6F2C20E6F51000B63AA6 /* SwiftyFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 693A6F2B20E6F50800B63AA6 /* SwiftyFeedback.framework */; }; 11 | 693A6F2D20E6F51000B63AA6 /* SwiftyFeedback.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 693A6F2B20E6F50800B63AA6 /* SwiftyFeedback.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 69E23BF820E6E0F100BD3AAE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E23BF720E6E0F100BD3AAE /* AppDelegate.swift */; }; 13 | 69E23BFA20E6E0F100BD3AAE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E23BF920E6E0F100BD3AAE /* ViewController.swift */; }; 14 | 69E23BFD20E6E0F100BD3AAE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69E23BFB20E6E0F100BD3AAE /* Main.storyboard */; }; 15 | 69E23BFF20E6E0F200BD3AAE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69E23BFE20E6E0F200BD3AAE /* Assets.xcassets */; }; 16 | 69E23C0220E6E0F200BD3AAE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69E23C0020E6E0F200BD3AAE /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 693A6F2A20E6F50800B63AA6 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 693A6F2620E6F50800B63AA6 /* SwiftyFeedback.xcodeproj */; 23 | proxyType = 2; 24 | remoteGlobalIDString = 693DBCED20E6DE6E00202D14; 25 | remoteInfo = SwiftyFeedback; 26 | }; 27 | 693A6F2E20E6F51000B63AA6 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 693A6F2620E6F50800B63AA6 /* SwiftyFeedback.xcodeproj */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 693DBCEC20E6DE6E00202D14; 32 | remoteInfo = SwiftyFeedback; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXCopyFilesBuildPhase section */ 37 | 69E23C1220E6E11200BD3AAE /* Embed Frameworks */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = ""; 41 | dstSubfolderSpec = 10; 42 | files = ( 43 | 693A6F2D20E6F51000B63AA6 /* SwiftyFeedback.framework in Embed Frameworks */, 44 | ); 45 | name = "Embed Frameworks"; 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXCopyFilesBuildPhase section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 693A6F2620E6F50800B63AA6 /* SwiftyFeedback.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftyFeedback.xcodeproj; path = ../SwiftyFeedback.xcodeproj; sourceTree = ""; }; 52 | 69E23BF420E6E0F100BD3AAE /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 69E23BF720E6E0F100BD3AAE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 54 | 69E23BF920E6E0F100BD3AAE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 55 | 69E23BFC20E6E0F100BD3AAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | 69E23BFE20E6E0F200BD3AAE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 69E23C0120E6E0F200BD3AAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 69E23C0320E6E0F200BD3AAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 69E23BF120E6E0F100BD3AAE /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 693A6F2C20E6F51000B63AA6 /* SwiftyFeedback.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 693A6F2720E6F50800B63AA6 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 693A6F2B20E6F50800B63AA6 /* SwiftyFeedback.framework */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 69E23BEB20E6E0F100BD3AAE = { 82 | isa = PBXGroup; 83 | children = ( 84 | 693A6F2620E6F50800B63AA6 /* SwiftyFeedback.xcodeproj */, 85 | 69E23BF620E6E0F100BD3AAE /* Example */, 86 | 69E23BF520E6E0F100BD3AAE /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 69E23BF520E6E0F100BD3AAE /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 69E23BF420E6E0F100BD3AAE /* Example.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 69E23BF620E6E0F100BD3AAE /* Example */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 69E23BF720E6E0F100BD3AAE /* AppDelegate.swift */, 102 | 69E23BF920E6E0F100BD3AAE /* ViewController.swift */, 103 | 69E23BFB20E6E0F100BD3AAE /* Main.storyboard */, 104 | 69E23BFE20E6E0F200BD3AAE /* Assets.xcassets */, 105 | 69E23C0020E6E0F200BD3AAE /* LaunchScreen.storyboard */, 106 | 69E23C0320E6E0F200BD3AAE /* Info.plist */, 107 | ); 108 | path = Example; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXNativeTarget section */ 114 | 69E23BF320E6E0F100BD3AAE /* Example */ = { 115 | isa = PBXNativeTarget; 116 | buildConfigurationList = 69E23C0620E6E0F200BD3AAE /* Build configuration list for PBXNativeTarget "Example" */; 117 | buildPhases = ( 118 | 69E23BF020E6E0F100BD3AAE /* Sources */, 119 | 69E23BF120E6E0F100BD3AAE /* Frameworks */, 120 | 69E23BF220E6E0F100BD3AAE /* Resources */, 121 | 69E23C1220E6E11200BD3AAE /* Embed Frameworks */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | 693A6F2F20E6F51000B63AA6 /* PBXTargetDependency */, 127 | ); 128 | name = Example; 129 | productName = Example; 130 | productReference = 69E23BF420E6E0F100BD3AAE /* Example.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | 69E23BEC20E6E0F100BD3AAE /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastSwiftUpdateCheck = 0930; 140 | LastUpgradeCheck = 0930; 141 | ORGANIZATIONNAME = "Juan Pablo Fernandez"; 142 | TargetAttributes = { 143 | 69E23BF320E6E0F100BD3AAE = { 144 | CreatedOnToolsVersion = 9.3; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 69E23BEF20E6E0F100BD3AAE /* Build configuration list for PBXProject "Example" */; 149 | compatibilityVersion = "Xcode 9.3"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = 69E23BEB20E6E0F100BD3AAE; 157 | productRefGroup = 69E23BF520E6E0F100BD3AAE /* Products */; 158 | projectDirPath = ""; 159 | projectReferences = ( 160 | { 161 | ProductGroup = 693A6F2720E6F50800B63AA6 /* Products */; 162 | ProjectRef = 693A6F2620E6F50800B63AA6 /* SwiftyFeedback.xcodeproj */; 163 | }, 164 | ); 165 | projectRoot = ""; 166 | targets = ( 167 | 69E23BF320E6E0F100BD3AAE /* Example */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXReferenceProxy section */ 173 | 693A6F2B20E6F50800B63AA6 /* SwiftyFeedback.framework */ = { 174 | isa = PBXReferenceProxy; 175 | fileType = wrapper.framework; 176 | path = SwiftyFeedback.framework; 177 | remoteRef = 693A6F2A20E6F50800B63AA6 /* PBXContainerItemProxy */; 178 | sourceTree = BUILT_PRODUCTS_DIR; 179 | }; 180 | /* End PBXReferenceProxy section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | 69E23BF220E6E0F100BD3AAE /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 69E23C0220E6E0F200BD3AAE /* LaunchScreen.storyboard in Resources */, 188 | 69E23BFF20E6E0F200BD3AAE /* Assets.xcassets in Resources */, 189 | 69E23BFD20E6E0F100BD3AAE /* Main.storyboard in Resources */, 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | /* End PBXResourcesBuildPhase section */ 194 | 195 | /* Begin PBXSourcesBuildPhase section */ 196 | 69E23BF020E6E0F100BD3AAE /* Sources */ = { 197 | isa = PBXSourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | 69E23BFA20E6E0F100BD3AAE /* ViewController.swift in Sources */, 201 | 69E23BF820E6E0F100BD3AAE /* AppDelegate.swift in Sources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXSourcesBuildPhase section */ 206 | 207 | /* Begin PBXTargetDependency section */ 208 | 693A6F2F20E6F51000B63AA6 /* PBXTargetDependency */ = { 209 | isa = PBXTargetDependency; 210 | name = SwiftyFeedback; 211 | targetProxy = 693A6F2E20E6F51000B63AA6 /* PBXContainerItemProxy */; 212 | }; 213 | /* End PBXTargetDependency section */ 214 | 215 | /* Begin PBXVariantGroup section */ 216 | 69E23BFB20E6E0F100BD3AAE /* Main.storyboard */ = { 217 | isa = PBXVariantGroup; 218 | children = ( 219 | 69E23BFC20E6E0F100BD3AAE /* Base */, 220 | ); 221 | name = Main.storyboard; 222 | sourceTree = ""; 223 | }; 224 | 69E23C0020E6E0F200BD3AAE /* LaunchScreen.storyboard */ = { 225 | isa = PBXVariantGroup; 226 | children = ( 227 | 69E23C0120E6E0F200BD3AAE /* Base */, 228 | ); 229 | name = LaunchScreen.storyboard; 230 | sourceTree = ""; 231 | }; 232 | /* End PBXVariantGroup section */ 233 | 234 | /* Begin XCBuildConfiguration section */ 235 | 69E23C0420E6E0F200BD3AAE /* Debug */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_ANALYZER_NONNULL = YES; 240 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_ENABLE_OBJC_WEAK = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INFINITE_RECURSION = YES; 256 | CLANG_WARN_INT_CONVERSION = YES; 257 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 259 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | CODE_SIGN_IDENTITY = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = dwarf; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu11; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 282 | GCC_WARN_UNDECLARED_SELECTOR = YES; 283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 284 | GCC_WARN_UNUSED_FUNCTION = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 287 | MTL_ENABLE_DEBUG_INFO = YES; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = iphoneos; 290 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 292 | }; 293 | name = Debug; 294 | }; 295 | 69E23C0520E6E0F200BD3AAE /* Release */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_ANALYZER_NONNULL = YES; 300 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 301 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 302 | CLANG_CXX_LIBRARY = "libc++"; 303 | CLANG_ENABLE_MODULES = YES; 304 | CLANG_ENABLE_OBJC_ARC = YES; 305 | CLANG_ENABLE_OBJC_WEAK = YES; 306 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 307 | CLANG_WARN_BOOL_CONVERSION = YES; 308 | CLANG_WARN_COMMA = YES; 309 | CLANG_WARN_CONSTANT_CONVERSION = YES; 310 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 311 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 312 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INFINITE_RECURSION = YES; 316 | CLANG_WARN_INT_CONVERSION = YES; 317 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 319 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | CODE_SIGN_IDENTITY = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 330 | ENABLE_NS_ASSERTIONS = NO; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu11; 333 | GCC_NO_COMMON_BLOCKS = YES; 334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 336 | GCC_WARN_UNDECLARED_SELECTOR = YES; 337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 338 | GCC_WARN_UNUSED_FUNCTION = YES; 339 | GCC_WARN_UNUSED_VARIABLE = YES; 340 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 341 | MTL_ENABLE_DEBUG_INFO = NO; 342 | SDKROOT = iphoneos; 343 | SWIFT_COMPILATION_MODE = wholemodule; 344 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 345 | VALIDATE_PRODUCT = YES; 346 | }; 347 | name = Release; 348 | }; 349 | 69E23C0720E6E0F200BD3AAE /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 353 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 354 | CODE_SIGN_STYLE = Automatic; 355 | DEVELOPMENT_TEAM = V92SZ8966T; 356 | INFOPLIST_FILE = Example/Info.plist; 357 | LD_RUNPATH_SEARCH_PATHS = ( 358 | "$(inherited)", 359 | "@executable_path/Frameworks", 360 | ); 361 | PRODUCT_BUNDLE_IDENTIFIER = com.juanpablofernandez.Example; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | SWIFT_VERSION = 4.0; 364 | TARGETED_DEVICE_FAMILY = "1,2"; 365 | }; 366 | name = Debug; 367 | }; 368 | 69E23C0820E6E0F200BD3AAE /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | CODE_SIGN_STYLE = Automatic; 374 | DEVELOPMENT_TEAM = V92SZ8966T; 375 | INFOPLIST_FILE = Example/Info.plist; 376 | LD_RUNPATH_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "@executable_path/Frameworks", 379 | ); 380 | PRODUCT_BUNDLE_IDENTIFIER = com.juanpablofernandez.Example; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SWIFT_VERSION = 4.0; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Release; 386 | }; 387 | /* End XCBuildConfiguration section */ 388 | 389 | /* Begin XCConfigurationList section */ 390 | 69E23BEF20E6E0F100BD3AAE /* Build configuration list for PBXProject "Example" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 69E23C0420E6E0F200BD3AAE /* Debug */, 394 | 69E23C0520E6E0F200BD3AAE /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | 69E23C0620E6E0F200BD3AAE /* Build configuration list for PBXNativeTarget "Example" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | 69E23C0720E6E0F200BD3AAE /* Debug */, 403 | 69E23C0820E6E0F200BD3AAE /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | /* End XCConfigurationList section */ 409 | }; 410 | rootObject = 69E23BEC20E6E0F100BD3AAE /* Project object */; 411 | } 412 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Jay.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanpablofernandez/SwiftyFeedback/0d7ff6cada3de30495f6e3a17d15352ad73f84e8/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Jay.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/Jay.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Juan Pablo on 6/29/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyFeedback 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | SwiftyFeedback.shared.recipients = ["test@gmail.com"] 20 | return true 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 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 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Juan Pablo on 6/29/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyFeedback 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | } 18 | @IBAction func contactTapped(_ sender: Any) { 19 | SwiftyFeedback.shared.present(self) 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Juan Pablo Fernandez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftyFeedback 2 | > A lightweight pure-Swift library that allows users to send feedback. 3 | 4 | [![Swift Version][swift-image]][swift-url] 5 | [![Build Status][travis-image]][travis-url] 6 | [![License][license-image]][license-url] 7 | [![CocoaPods](https://img.shields.io/cocoapods/v/SwiftyFeedback.svg)](https://cocoapods.org/pods/SwiftyFeedback) 8 | [![Platform](https://img.shields.io/cocoapods/p/SwiftyFeedback.svg?style=flat)](https://cocoapods.org/pods/SwiftyFeedback) 9 | 10 | 11 | SwiftyFeedback is a lightweight pure-Swift library based on the [CTFeedback](https://github.com/rizumita/CTFeedback) library. 12 | 13 | SwiftyFeedback makes it easy to add a simple feedback template to any of your apps. 14 | 15 | ![](Screenshots/screenshot1.PNG) 16 | 17 | ## Contents 18 | 19 | * [Requirements](#requirements) 20 | * [Installation](#installation) 21 | * [CocoaPods](#cocoapods) 22 | * [Manually](#manually) 23 | * [Usage](#usage) 24 | * [Notes](#notes) 25 | * [Contribute](#contribute) 26 | * [License](#license) 27 | 28 | ## Requirements 29 | 30 | - iOS 9.0+ 31 | - Xcode 7.3+ 32 | - Swift 4.0+ 33 | 34 | ## Installation 35 | 36 | #### CocoaPods 37 | You can use [CocoaPods](http://cocoapods.org/) to install `SwiftyFeedback` by adding this to your `Podfile`: 38 | 39 | ```ruby 40 | use_frameworks! 41 | pod 'SwiftyFeedback' 42 | ``` 43 | If you get the ``Unable to find a specification for `SwiftyFeedback`.`` error after running `pod install`. 44 | 45 | Run the following commands on your project directory: 46 | ``` 47 | pod repo update 48 | ``` 49 | ``` 50 | pod install 51 | ``` 52 | #### Manually 53 | 1. Drag and drop ```SwiftyFeedback.swift``` and the rest of the ```.swift``` files into your project. 54 | 2. That's it! 55 | 56 | ## Usage 57 | 1. Import `SwiftyFeedback` module to your `AppDelegate` class 58 | ```swift 59 | import SwiftyFeedback 60 | ``` 61 | 2. In your `AppDelegate` class, add the recipients array to the SwiftyFeedback singleton. 62 | ```swift 63 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 64 | SwiftyFeedback.shared.recipients = ["test@gmail.com"] 65 | return true 66 | } 67 | ``` 68 | 3. Import `SwiftyFeedback` module to your `ViewController` class 69 | ```swift 70 | import SwiftyFeedback 71 | ``` 72 | 4. Present the `SwiftyFeedback` view on your `ViewController` e.g. 73 | ```swift 74 | class ViewController: UIViewController { 75 | 76 | @IBAction func contactTapped(_ sender: Any) { 77 | SwiftyFeedback.shared.present(self) 78 | } 79 | } 80 | ``` 81 | 5. `SwiftyFeedback` works with default implementation. Override it to customize its behavior 82 | 83 | 84 | 85 | ## Notes 86 | * Landscape mode is not supported 87 | 88 | ## Contribute 89 | Contributions are welcomed! There are however certain guidelines you must follow when you contribute: 90 | * Have descriptive commit messages. 91 | * Make a pull request for every feature (Don't make a pull request that adds 3 new features. Make an individual pull request for each of those features, with a descriptive message). 92 | * Don't update the example project, or any other irrelevant files. 93 | 94 | 95 | ## License 96 | 97 | Distributed under the MIT license. See ``LICENSE`` for more information. 98 | 99 | [swift-image]:https://img.shields.io/badge/swift-3.0-orange.svg 100 | [swift-url]: https://swift.org/ 101 | [license-image]: https://img.shields.io/badge/License-MIT-blue.svg 102 | [license-url]: LICENSE 103 | [travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square 104 | [travis-url]: https://travis-ci.org/dbader/node-datadog-metrics 105 | [codebeat-image]: https://codebeat.co/badges/c19b47ea-2f9d-45df-8458-b2d952fe9dad 106 | [codebeat-url]: https://codebeat.co/projects/github-com-vsouza-awesomeios-com 107 | -------------------------------------------------------------------------------- /Screenshots/screenshot1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanpablofernandez/SwiftyFeedback/0d7ff6cada3de30495f6e3a17d15352ad73f84e8/Screenshots/screenshot1.PNG -------------------------------------------------------------------------------- /SwiftyFeedback.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SwiftyFeedback" 4 | s.version = "1.0.1" 5 | s.summary = "A lightweight framework that allows users to contact developers." 6 | s.description = <<-DESC 7 | SwiftyFeedback makes it easy to add a contact screen to any of your apps. This allows users to contact you through email, all in a lightweight framework. 8 | DESC 9 | s.homepage = "https://github.com/juanpablofernandez/SwiftyFeedback" 10 | s.license = { :type => 'MIT', :file => 'LICENSE.md' } 11 | s.author = "Juan Pablo Fernandez" 12 | s.social_media_url = "https://github.com/juanpablofernandez" 13 | s.ios.deployment_target = '9.0' 14 | s.source = { :git => "https://github.com/juanpablofernandez/SwiftyFeedback.git", :tag => "#{s.version}" } 15 | s.source_files = "SwiftyFeedback", "SwiftyFeedback/**/*.{swift}" 16 | s.swift_version = '4.0' 17 | 18 | end 19 | -------------------------------------------------------------------------------- /SwiftyFeedback.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 693DBCF220E6DE6E00202D14 /* SwiftyFeedback.h in Headers */ = {isa = PBXBuildFile; fileRef = 693DBCF020E6DE6E00202D14 /* SwiftyFeedback.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 69E23BE220E6E09600BD3AAE /* SwiftyFeedbackEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCF820E6DE8B00202D14 /* SwiftyFeedbackEmail.swift */; }; 12 | 69E23BE320E6E09600BD3AAE /* SwiftyFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCFA20E6DE8C00202D14 /* SwiftyFeedback.swift */; }; 13 | 69E23BE420E6E09600BD3AAE /* SwiftyFeedbackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCFB20E6DE8C00202D14 /* SwiftyFeedbackController.swift */; }; 14 | 69E23BE520E6E09600BD3AAE /* SwiftyFeedbackAttachmentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCF920E6DE8C00202D14 /* SwiftyFeedbackAttachmentCell.swift */; }; 15 | 69E23BE620E6E09600BD3AAE /* SwiftyFeedbackCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCFC20E6DE8C00202D14 /* SwiftyFeedbackCell.swift */; }; 16 | 69E23BE720E6E09600BD3AAE /* SwiftyFeedbackExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693DBCFD20E6DE8C00202D14 /* SwiftyFeedbackExtensions.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 693DBCED20E6DE6E00202D14 /* SwiftyFeedback.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyFeedback.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 693DBCF020E6DE6E00202D14 /* SwiftyFeedback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyFeedback.h; sourceTree = ""; }; 22 | 693DBCF120E6DE6E00202D14 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | 693DBCF820E6DE8B00202D14 /* SwiftyFeedbackEmail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedbackEmail.swift; sourceTree = ""; }; 24 | 693DBCF920E6DE8C00202D14 /* SwiftyFeedbackAttachmentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedbackAttachmentCell.swift; sourceTree = ""; }; 25 | 693DBCFA20E6DE8C00202D14 /* SwiftyFeedback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedback.swift; sourceTree = ""; }; 26 | 693DBCFB20E6DE8C00202D14 /* SwiftyFeedbackController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedbackController.swift; sourceTree = ""; }; 27 | 693DBCFC20E6DE8C00202D14 /* SwiftyFeedbackCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedbackCell.swift; sourceTree = ""; }; 28 | 693DBCFD20E6DE8C00202D14 /* SwiftyFeedbackExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyFeedbackExtensions.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 693DBCE920E6DE6E00202D14 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 693DBCE320E6DE6E00202D14 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 693DBCEF20E6DE6E00202D14 /* SwiftyFeedback */, 46 | 693DBCEE20E6DE6E00202D14 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 693DBCEE20E6DE6E00202D14 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 693DBCED20E6DE6E00202D14 /* SwiftyFeedback.framework */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 693DBCEF20E6DE6E00202D14 /* SwiftyFeedback */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 693DBCF020E6DE6E00202D14 /* SwiftyFeedback.h */, 62 | 693DBCF820E6DE8B00202D14 /* SwiftyFeedbackEmail.swift */, 63 | 693DBCFA20E6DE8C00202D14 /* SwiftyFeedback.swift */, 64 | 693DBCFB20E6DE8C00202D14 /* SwiftyFeedbackController.swift */, 65 | 693DBCF920E6DE8C00202D14 /* SwiftyFeedbackAttachmentCell.swift */, 66 | 693DBCFC20E6DE8C00202D14 /* SwiftyFeedbackCell.swift */, 67 | 693DBCFD20E6DE8C00202D14 /* SwiftyFeedbackExtensions.swift */, 68 | 693DBCF120E6DE6E00202D14 /* Info.plist */, 69 | ); 70 | path = SwiftyFeedback; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXHeadersBuildPhase section */ 76 | 693DBCEA20E6DE6E00202D14 /* Headers */ = { 77 | isa = PBXHeadersBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 693DBCF220E6DE6E00202D14 /* SwiftyFeedback.h in Headers */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXHeadersBuildPhase section */ 85 | 86 | /* Begin PBXNativeTarget section */ 87 | 693DBCEC20E6DE6E00202D14 /* SwiftyFeedback */ = { 88 | isa = PBXNativeTarget; 89 | buildConfigurationList = 693DBCF520E6DE6E00202D14 /* Build configuration list for PBXNativeTarget "SwiftyFeedback" */; 90 | buildPhases = ( 91 | 693DBCE820E6DE6E00202D14 /* Sources */, 92 | 693DBCE920E6DE6E00202D14 /* Frameworks */, 93 | 693DBCEA20E6DE6E00202D14 /* Headers */, 94 | 693DBCEB20E6DE6E00202D14 /* Resources */, 95 | ); 96 | buildRules = ( 97 | ); 98 | dependencies = ( 99 | ); 100 | name = SwiftyFeedback; 101 | productName = SwiftyFeedback; 102 | productReference = 693DBCED20E6DE6E00202D14 /* SwiftyFeedback.framework */; 103 | productType = "com.apple.product-type.framework"; 104 | }; 105 | /* End PBXNativeTarget section */ 106 | 107 | /* Begin PBXProject section */ 108 | 693DBCE420E6DE6E00202D14 /* Project object */ = { 109 | isa = PBXProject; 110 | attributes = { 111 | LastUpgradeCheck = 0930; 112 | ORGANIZATIONNAME = "Juan Pablo Fernandez"; 113 | TargetAttributes = { 114 | 693DBCEC20E6DE6E00202D14 = { 115 | CreatedOnToolsVersion = 9.3; 116 | }; 117 | }; 118 | }; 119 | buildConfigurationList = 693DBCE720E6DE6E00202D14 /* Build configuration list for PBXProject "SwiftyFeedback" */; 120 | compatibilityVersion = "Xcode 9.3"; 121 | developmentRegion = en; 122 | hasScannedForEncodings = 0; 123 | knownRegions = ( 124 | en, 125 | ); 126 | mainGroup = 693DBCE320E6DE6E00202D14; 127 | productRefGroup = 693DBCEE20E6DE6E00202D14 /* Products */; 128 | projectDirPath = ""; 129 | projectRoot = ""; 130 | targets = ( 131 | 693DBCEC20E6DE6E00202D14 /* SwiftyFeedback */, 132 | ); 133 | }; 134 | /* End PBXProject section */ 135 | 136 | /* Begin PBXResourcesBuildPhase section */ 137 | 693DBCEB20E6DE6E00202D14 /* Resources */ = { 138 | isa = PBXResourcesBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | 693DBCE820E6DE6E00202D14 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 69E23BE620E6E09600BD3AAE /* SwiftyFeedbackCell.swift in Sources */, 152 | 69E23BE720E6E09600BD3AAE /* SwiftyFeedbackExtensions.swift in Sources */, 153 | 69E23BE520E6E09600BD3AAE /* SwiftyFeedbackAttachmentCell.swift in Sources */, 154 | 69E23BE320E6E09600BD3AAE /* SwiftyFeedback.swift in Sources */, 155 | 69E23BE220E6E09600BD3AAE /* SwiftyFeedbackEmail.swift in Sources */, 156 | 69E23BE420E6E09600BD3AAE /* SwiftyFeedbackController.swift in Sources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXSourcesBuildPhase section */ 161 | 162 | /* Begin XCBuildConfiguration section */ 163 | 693DBCF320E6DE6E00202D14 /* Debug */ = { 164 | isa = XCBuildConfiguration; 165 | buildSettings = { 166 | ALWAYS_SEARCH_USER_PATHS = NO; 167 | CLANG_ANALYZER_NONNULL = YES; 168 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 169 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 170 | CLANG_CXX_LIBRARY = "libc++"; 171 | CLANG_ENABLE_MODULES = YES; 172 | CLANG_ENABLE_OBJC_ARC = YES; 173 | CLANG_ENABLE_OBJC_WEAK = YES; 174 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 175 | CLANG_WARN_BOOL_CONVERSION = YES; 176 | CLANG_WARN_COMMA = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INFINITE_RECURSION = YES; 184 | CLANG_WARN_INT_CONVERSION = YES; 185 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 186 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 190 | CLANG_WARN_STRICT_PROTOTYPES = YES; 191 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 192 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 193 | CLANG_WARN_UNREACHABLE_CODE = YES; 194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 195 | CODE_SIGN_IDENTITY = "iPhone Developer"; 196 | COPY_PHASE_STRIP = NO; 197 | CURRENT_PROJECT_VERSION = 1; 198 | DEBUG_INFORMATION_FORMAT = dwarf; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | ENABLE_TESTABILITY = YES; 201 | GCC_C_LANGUAGE_STANDARD = gnu11; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_NO_COMMON_BLOCKS = YES; 204 | GCC_OPTIMIZATION_LEVEL = 0; 205 | GCC_PREPROCESSOR_DEFINITIONS = ( 206 | "DEBUG=1", 207 | "$(inherited)", 208 | ); 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 216 | MTL_ENABLE_DEBUG_INFO = YES; 217 | ONLY_ACTIVE_ARCH = YES; 218 | SDKROOT = iphoneos; 219 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 220 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 221 | VERSIONING_SYSTEM = "apple-generic"; 222 | VERSION_INFO_PREFIX = ""; 223 | }; 224 | name = Debug; 225 | }; 226 | 693DBCF420E6DE6E00202D14 /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_ENABLE_OBJC_WEAK = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | CODE_SIGN_IDENTITY = "iPhone Developer"; 259 | COPY_PHASE_STRIP = NO; 260 | CURRENT_PROJECT_VERSION = 1; 261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 262 | ENABLE_NS_ASSERTIONS = NO; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | GCC_C_LANGUAGE_STANDARD = gnu11; 265 | GCC_NO_COMMON_BLOCKS = YES; 266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 268 | GCC_WARN_UNDECLARED_SELECTOR = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 270 | GCC_WARN_UNUSED_FUNCTION = YES; 271 | GCC_WARN_UNUSED_VARIABLE = YES; 272 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 273 | MTL_ENABLE_DEBUG_INFO = NO; 274 | SDKROOT = iphoneos; 275 | SWIFT_COMPILATION_MODE = wholemodule; 276 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 277 | VALIDATE_PRODUCT = YES; 278 | VERSIONING_SYSTEM = "apple-generic"; 279 | VERSION_INFO_PREFIX = ""; 280 | }; 281 | name = Release; 282 | }; 283 | 693DBCF620E6DE6E00202D14 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | CODE_SIGN_IDENTITY = ""; 287 | CODE_SIGN_STYLE = Automatic; 288 | DEFINES_MODULE = YES; 289 | DEVELOPMENT_TEAM = V92SZ8966T; 290 | DYLIB_COMPATIBILITY_VERSION = 1; 291 | DYLIB_CURRENT_VERSION = 1; 292 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 293 | INFOPLIST_FILE = SwiftyFeedback/Info.plist; 294 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 295 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 296 | LD_RUNPATH_SEARCH_PATHS = ( 297 | "$(inherited)", 298 | "@executable_path/Frameworks", 299 | "@loader_path/Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = com.juanpablofernandez.SwiftyFeedback; 302 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 303 | SKIP_INSTALL = YES; 304 | SWIFT_VERSION = 4.0; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | }; 307 | name = Debug; 308 | }; 309 | 693DBCF720E6DE6E00202D14 /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | CODE_SIGN_IDENTITY = ""; 313 | CODE_SIGN_STYLE = Automatic; 314 | DEFINES_MODULE = YES; 315 | DEVELOPMENT_TEAM = V92SZ8966T; 316 | DYLIB_COMPATIBILITY_VERSION = 1; 317 | DYLIB_CURRENT_VERSION = 1; 318 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 319 | INFOPLIST_FILE = SwiftyFeedback/Info.plist; 320 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 321 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/Frameworks", 325 | "@loader_path/Frameworks", 326 | ); 327 | PRODUCT_BUNDLE_IDENTIFIER = com.juanpablofernandez.SwiftyFeedback; 328 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 329 | SKIP_INSTALL = YES; 330 | SWIFT_VERSION = 4.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | 693DBCE720E6DE6E00202D14 /* Build configuration list for PBXProject "SwiftyFeedback" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 693DBCF320E6DE6E00202D14 /* Debug */, 342 | 693DBCF420E6DE6E00202D14 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | 693DBCF520E6DE6E00202D14 /* Build configuration list for PBXNativeTarget "SwiftyFeedback" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | 693DBCF620E6DE6E00202D14 /* Debug */, 351 | 693DBCF720E6DE6E00202D14 /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = 693DBCE420E6DE6E00202D14 /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /SwiftyFeedback.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftyFeedback.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftyFeedback.xcodeproj/project.xcworkspace/xcuserdata/Jay.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanpablofernandez/SwiftyFeedback/0d7ff6cada3de30495f6e3a17d15352ad73f84e8/SwiftyFeedback.xcodeproj/project.xcworkspace/xcuserdata/Jay.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftyFeedback.xcodeproj/xcuserdata/Jay.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftyFeedback.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftyFeedback/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedback.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedback.h 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/29/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftyFeedback. 12 | FOUNDATION_EXPORT double SwiftyFeedbackVersionNumber; 13 | 14 | //! Project version string for SwiftyFeedback. 15 | FOUNDATION_EXPORT const unsigned char SwiftyFeedbackVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedback.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/29/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class SwiftyFeedback { 12 | 13 | static public let shared: SwiftyFeedback = SwiftyFeedback() 14 | 15 | public var recipients: [String] = [] 16 | public var title: String = "Contact Us" 17 | public var sendButtonTitle = "Send" 18 | public var cancelButtonTitle = "Cancel" 19 | public var navigationController = UINavigationController() 20 | 21 | public func present(_ sender: UIViewController) { 22 | navigationController.viewControllers = [SwiftyFeedbackViewController()] 23 | sender.present(navigationController, animated: true, completion: nil) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedbackAttachmentCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedbackAttachmentCell.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/28/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal class AttachmentTableViewCell: UITableViewCell { 12 | 13 | var attachmentImageView: UIImageView = { 14 | let view = UIImageView() 15 | view.contentMode = .scaleAspectFill 16 | view.clipsToBounds = true 17 | view.layer.borderColor = UIColor.lightGray.cgColor 18 | view.layer.borderWidth = 1 19 | view.layer.masksToBounds = true 20 | // view.layer.cornerRadius = 18 21 | view.translatesAutoresizingMaskIntoConstraints = false 22 | return view 23 | }() 24 | 25 | var attachmentLabel: UILabel = { 26 | let label = UILabel() 27 | label.translatesAutoresizingMaskIntoConstraints = false 28 | return label 29 | }() 30 | 31 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 32 | super.init(style: style, reuseIdentifier: reuseIdentifier) 33 | setup() 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | private func setup() { 41 | 42 | //ImageView 43 | self.addSubview(attachmentImageView) 44 | attachmentImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 15).isActive = true 45 | attachmentImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true 46 | attachmentImageView.widthAnchor.constraint(equalToConstant: 36).isActive = true 47 | attachmentImageView.heightAnchor.constraint(equalToConstant: 36).isActive = true 48 | 49 | //Label 50 | self.addSubview(attachmentLabel) 51 | attachmentLabel.leftAnchor.constraint(equalTo: attachmentImageView.rightAnchor, constant: 10).isActive = true 52 | attachmentLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true 53 | attachmentLabel.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true 54 | attachmentImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedbackCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedbackCell.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/29/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal class FeedbackTableViewCell: UITableViewCell { 12 | 13 | var textView: UITextView = { 14 | let view = UITextView() 15 | view.font = UIFont.systemFont(ofSize: 17) 16 | view.sizeToFit() 17 | view.isScrollEnabled = false 18 | view.translatesAutoresizingMaskIntoConstraints = false 19 | return view 20 | }() 21 | 22 | var placeholderView: UITextView = { 23 | let view = UITextView() 24 | view.text = "Describe your question." 25 | view.textColor = UIColor.lightGray 26 | view.font = UIFont.systemFont(ofSize: 17) 27 | view.sizeToFit() 28 | view.isScrollEnabled = false 29 | view.isUserInteractionEnabled = false 30 | view.translatesAutoresizingMaskIntoConstraints = false 31 | return view 32 | }() 33 | 34 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | setup() 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | func setup() { 44 | 45 | //FeedbackTextView Setup 46 | textView.delegate = self 47 | self.addSubview(textView) 48 | let cellMargins = self.layoutMarginsGuide 49 | textView.leftAnchor.constraint(equalTo: cellMargins.leftAnchor).isActive = true 50 | textView.rightAnchor.constraint(equalTo: cellMargins.rightAnchor).isActive = true 51 | textView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 52 | textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -44).isActive = true 53 | textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0).isActive = true 54 | 55 | //Feedback Placeholder 56 | textView.insertSubview(placeholderView, at: 0) 57 | placeholderView.isHidden = !textView.text.isEmpty 58 | textView.leftAnchor.constraint(equalTo: placeholderView.leftAnchor).isActive = true 59 | textView.rightAnchor.constraint(equalTo: placeholderView.rightAnchor).isActive = true 60 | textView.topAnchor.constraint(equalTo: placeholderView.topAnchor).isActive = true 61 | textView.bottomAnchor.constraint(equalTo: placeholderView.bottomAnchor).isActive = true 62 | } 63 | } 64 | 65 | extension FeedbackTableViewCell: UITextViewDelegate { 66 | 67 | func textViewDidChange(_ textView: UITextView) { 68 | placeholderView.isHidden = !textView.text.isEmpty 69 | NotificationCenter.default.post(name: Notification.Name("UpdatedFeedbackTextView"), object: nil) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedbackController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedbackController.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/28/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import MessageUI 12 | 13 | internal class SwiftyFeedbackViewController: UITableViewController { 14 | 15 | // MARK: Cells 16 | 17 | private var topicCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 18 | private var feedbackCell: FeedbackTableViewCell = FeedbackTableViewCell() 19 | private var attachmentCell: UITableViewCell = UITableViewCell() 20 | private var deviceCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 21 | private var iosCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 22 | private var nameCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 23 | private var versionCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 24 | private var buildCell: UITableViewCell = UITableViewCell(style: .value1 , reuseIdentifier: nil) 25 | 26 | 27 | // MARK: Variables 28 | 29 | private var imagePicker = UIImagePickerController() 30 | private var attachments: [UIImage] = [] 31 | private var selectedAttachment: IndexPath = IndexPath(row: 1, section: 1) 32 | private var email: EmailModel = EmailModel() 33 | 34 | 35 | // MARK: Setup 36 | 37 | override internal func viewDidLoad() { 38 | super.viewDidLoad() 39 | setup() 40 | tableViewSetup() 41 | } 42 | 43 | deinit { 44 | NotificationCenter.default.removeObserver(self) 45 | } 46 | 47 | private func setup() { 48 | title = "Contact Us" 49 | UIApplication.shared.statusBarStyle = .default 50 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Send", style: .plain, target: self, action: #selector(sendEmail)) 51 | 52 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleDismiss)) 53 | NotificationCenter.default.addObserver(self, selector: #selector(updateTableViewContentOffsetForTextView), name: Notification.Name("UpdatedFeedbackTextView"), object: nil) 54 | } 55 | 56 | @objc private func handleDismiss() { 57 | self.dismiss(animated: true, completion: nil) 58 | } 59 | 60 | @objc private func sendEmail() { 61 | email.attachments = attachments 62 | email.subject = "\(email.appName) - \(topicCell.detailTextLabel!.text!)" 63 | email.body = feedbackCell.textView.text 64 | composeEmail() 65 | } 66 | 67 | 68 | // MARK: TableView Setup 69 | 70 | private func tableViewSetup() { 71 | 72 | self.tableView = UITableView(frame: self.tableView.frame, style: .grouped) 73 | tableView.estimatedRowHeight = 44.0 74 | tableView.rowHeight = UITableViewAutomaticDimension 75 | tableView.keyboardDismissMode = .onDrag 76 | 77 | let cells = [topicCell, feedbackCell, attachmentCell, deviceCell, iosCell, nameCell, versionCell, buildCell] 78 | let cellsText = ["Select topic", "" , "Attach Photo", "Device", "iOS", "Name", "Version", "Build"] 79 | 80 | for index in cells.indices { 81 | let cell = cells[index] 82 | cell.selectionStyle = .none 83 | cell.textLabel?.text = cellsText[index] 84 | } 85 | 86 | topicCell.detailTextLabel?.text = "Question" 87 | topicCell.accessoryType = .disclosureIndicator 88 | topicCell.selectionStyle = .default 89 | 90 | attachmentCell.accessoryType = .disclosureIndicator 91 | attachmentCell.selectionStyle = .default 92 | 93 | //Device Info 94 | deviceCell.detailTextLabel?.text = email.deviceModel 95 | iosCell.detailTextLabel?.text = email.deviceOs 96 | 97 | //App Info 98 | nameCell.detailTextLabel?.text = email.appName 99 | versionCell.detailTextLabel?.text = email.appVersion 100 | buildCell.detailTextLabel?.text = email.appBuild 101 | } 102 | 103 | @objc private func updateTableViewContentOffsetForTextView() { 104 | let currentOffset = tableView.contentOffset 105 | UIView.setAnimationsEnabled(false) 106 | tableView.beginUpdates() 107 | tableView.endUpdates() 108 | UIView.setAnimationsEnabled(true) 109 | tableView.setContentOffset(currentOffset, animated: false) 110 | tableView.updateConstraintsIfNeeded() 111 | } 112 | 113 | 114 | // MARK: Subject Options 115 | 116 | private func presentSubjects() { 117 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 118 | alertController.addAction(UIAlertAction(title: "Question", style: .default, handler: selectedTopic)) 119 | alertController.addAction(UIAlertAction(title: "Request", style: .default, handler: selectedTopic)) 120 | alertController.addAction(UIAlertAction(title: "Bug Report", style: .default, handler: selectedTopic)) 121 | alertController.addAction(UIAlertAction(title: "Other", style: .default, handler: selectedTopic)) 122 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 123 | self.present(alertController, animated: true, completion: nil) 124 | } 125 | 126 | private func selectedTopic(action: UIAlertAction) { 127 | guard let text = action.title else { return } 128 | topicCell.detailTextLabel?.text = text 129 | email.subject = text 130 | feedbackCell.placeholderView.text = "Describe your \(text.lowercased())." 131 | } 132 | 133 | 134 | // MARK: Attachment Options 135 | 136 | private func presentAttachmentOptions(indexPath: IndexPath) { 137 | selectedAttachment = indexPath 138 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 139 | alertController.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: deleteAttachment)) 140 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 141 | self.present(alertController, animated: true, completion: nil) 142 | } 143 | 144 | private func deleteAttachment(action: UIAlertAction) { 145 | tableView.beginUpdates() 146 | attachments.remove(at: selectedAttachment.row) 147 | tableView.deleteRows(at: [selectedAttachment], with: .automatic) 148 | tableView.endUpdates() 149 | } 150 | } 151 | 152 | extension SwiftyFeedbackViewController { 153 | 154 | // MARK: Tableview Delegate: 155 | 156 | internal override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 157 | 158 | if indexPath.section == 0 && indexPath.row == 1 { 159 | return UITableViewAutomaticDimension 160 | } 161 | 162 | return 44.0 163 | } 164 | 165 | // MARK: TableView Datasource 166 | 167 | internal override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 168 | switch(section) { 169 | case 0: return "Topic" 170 | case 1: return "Attachments" 171 | case 2: return "Device Info" 172 | case 3: return "App Info" 173 | default: fatalError("Unknown section") 174 | } 175 | } 176 | 177 | internal override func numberOfSections(in tableView: UITableView) -> Int { 178 | return 4 179 | } 180 | 181 | internal override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 182 | switch(section) { 183 | case 0: return 2 184 | case 1: return attachments.count + 1 185 | case 2: return 2 186 | case 3: return 3 187 | default: fatalError("Unknown number of sections") 188 | } 189 | } 190 | 191 | internal override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 192 | tableView.deselectRow(at: indexPath, animated: true) 193 | 194 | if indexPath.section == 0 && indexPath.row == 0 { 195 | presentSubjects() 196 | } else if indexPath.section == 1 && indexPath.row == attachments.count { 197 | openPhotos() 198 | } else if indexPath.section == 1 && indexPath.row != attachments.count { 199 | presentAttachmentOptions(indexPath: indexPath) 200 | } 201 | } 202 | 203 | internal override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 204 | switch(indexPath.section) { 205 | case 0: 206 | switch(indexPath.row) { 207 | case 0: return self.topicCell 208 | case 1: return self.feedbackCell 209 | default: fatalError("Unknown row in section 0") 210 | } 211 | case 1: 212 | if attachments.count == 0 { return attachmentCell } 213 | if indexPath.row == attachments.count { return attachmentCell} 214 | let cell = AttachmentTableViewCell() 215 | cell.attachmentLabel.text = "Attachment \(indexPath.row + 1)" 216 | cell.attachmentImageView.image = attachments[indexPath.row] 217 | return cell 218 | 219 | case 2: 220 | switch(indexPath.row) { 221 | case 0: return self.deviceCell 222 | case 1: return self.iosCell 223 | default: fatalError("Unknown row in section 2") 224 | } 225 | case 3: 226 | switch(indexPath.row) { 227 | case 0: return self.nameCell 228 | case 1: return self.versionCell 229 | case 2: return self.buildCell 230 | default: fatalError("Unknown row in section 3") 231 | } 232 | default: fatalError("Unknown section") 233 | } 234 | } 235 | } 236 | 237 | extension SwiftyFeedbackViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate { 238 | 239 | private func openPhotos() { 240 | if attachments.count >= 5 { return } 241 | if UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum){ 242 | print("Button capture") 243 | 244 | imagePicker.delegate = self 245 | imagePicker.sourceType = .savedPhotosAlbum; 246 | imagePicker.allowsEditing = false 247 | 248 | self.present(imagePicker, animated: true, completion: nil) 249 | } 250 | } 251 | 252 | internal func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 253 | if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage { 254 | attachments.append(pickedImage) 255 | } 256 | 257 | tableView.reloadData() 258 | dismiss(animated: true, completion: nil) 259 | } 260 | 261 | internal func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 262 | dismiss(animated: true, completion:nil) 263 | } 264 | } 265 | 266 | extension SwiftyFeedbackViewController: MFMailComposeViewControllerDelegate { 267 | 268 | private func composeEmail() { 269 | 270 | let composeVC = MFMailComposeViewController() 271 | composeVC.mailComposeDelegate = self 272 | 273 | // Configure the fields of the interface. 274 | composeVC.setToRecipients(SwiftyFeedback.shared.recipients) 275 | composeVC.setSubject(email.subject) 276 | 277 | let info = "Device: \(email.deviceModel)\n" + "iOS: \(email.deviceOs)\n" + "App Name: \(email.appName)\n" + "App Version: \(email.appVersion)\n" + "App Build: \(email.appBuild)\n" 278 | 279 | let body = "\(email.body)\n\n" + info 280 | 281 | composeVC.setMessageBody(body, isHTML: false) 282 | 283 | for index in email.attachments.indices { 284 | let attachment = email.attachments[index] 285 | composeVC.addAttachmentData(UIImageJPEGRepresentation(attachment, CGFloat(1.0))!, mimeType: "image/jpeg", fileName: "attachment\(index+1).jpeg") 286 | } 287 | 288 | // Present the view controller modally. 289 | self.present(composeVC, animated: true, completion: nil) 290 | } 291 | 292 | internal func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { 293 | 294 | if result == .sent { 295 | controller.dismiss(animated: true, completion: nil) 296 | self.dismiss(animated: true, completion: nil) 297 | } else { 298 | controller.dismiss(animated: true, completion: nil) 299 | } 300 | } 301 | } 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedbackEmail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedbackEmail.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/28/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EmailModel { 12 | 13 | var subject: String = "" 14 | var body: String = "" 15 | var attachments: [UIImage] = [] 16 | 17 | var deviceModel: String = UIDevice.modelName 18 | var deviceOs: String = UIDevice.current.systemVersion 19 | 20 | var appName: String = Bundle.main.name 21 | var appVersion: String = Bundle.main.releaseVersionNumber 22 | var appBuild: String = Bundle.main.buildVersionNumber 23 | } 24 | -------------------------------------------------------------------------------- /SwiftyFeedback/SwiftyFeedbackExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyFeedbackExtensions.swift 3 | // SwiftyFeedback 4 | // 5 | // Created by Juan Pablo on 6/28/18. 6 | // Copyright © 2018 Juan Pablo Fernandez. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension Bundle { 13 | var releaseVersionNumber: String { 14 | return infoDictionary?["CFBundleShortVersionString"] as? String ?? "" 15 | } 16 | 17 | var buildVersionNumber: String { 18 | return infoDictionary?["CFBundleVersion"] as? String ?? "" 19 | } 20 | 21 | var displayName: String { 22 | return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" 23 | } 24 | 25 | var name: String { 26 | return object(forInfoDictionaryKey: "CFBundleName") as? String ?? "" 27 | } 28 | } 29 | 30 | public extension UIDevice { 31 | 32 | static let modelName: String = { 33 | var systemInfo = utsname() 34 | uname(&systemInfo) 35 | let machineMirror = Mirror(reflecting: systemInfo.machine) 36 | let identifier = machineMirror.children.reduce("") { identifier, element in 37 | guard let value = element.value as? Int8, value != 0 else { return identifier } 38 | return identifier + String(UnicodeScalar(UInt8(value))) 39 | } 40 | 41 | func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity 42 | #if os(iOS) 43 | switch identifier { 44 | case "iPod5,1": return "iPod Touch 5" 45 | case "iPod7,1": return "iPod Touch 6" 46 | case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" 47 | case "iPhone4,1": return "iPhone 4s" 48 | case "iPhone5,1", "iPhone5,2": return "iPhone 5" 49 | case "iPhone5,3", "iPhone5,4": return "iPhone 5c" 50 | case "iPhone6,1", "iPhone6,2": return "iPhone 5s" 51 | case "iPhone7,2": return "iPhone 6" 52 | case "iPhone7,1": return "iPhone 6 Plus" 53 | case "iPhone8,1": return "iPhone 6s" 54 | case "iPhone8,2": return "iPhone 6s Plus" 55 | case "iPhone9,1", "iPhone9,3": return "iPhone 7" 56 | case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" 57 | case "iPhone8,4": return "iPhone SE" 58 | case "iPhone10,1", "iPhone10,4": return "iPhone 8" 59 | case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" 60 | case "iPhone10,3", "iPhone10,6": return "iPhone X" 61 | case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2" 62 | case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3" 63 | case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4" 64 | case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" 65 | case "iPad5,3", "iPad5,4": return "iPad Air 2" 66 | case "iPad6,11", "iPad6,12": return "iPad 5" 67 | case "iPad7,5", "iPad7,6": return "iPad 6" 68 | case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini" 69 | case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2" 70 | case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3" 71 | case "iPad5,1", "iPad5,2": return "iPad Mini 4" 72 | case "iPad6,3", "iPad6,4": return "iPad Pro 9.7 Inch" 73 | case "iPad6,7", "iPad6,8": return "iPad Pro 12.9 Inch" 74 | case "iPad7,1", "iPad7,2": return "iPad Pro 12.9 Inch 2. Generation" 75 | case "iPad7,3", "iPad7,4": return "iPad Pro 10.5 Inch" 76 | case "AppleTV5,3": return "Apple TV" 77 | case "AppleTV6,2": return "Apple TV 4K" 78 | case "AudioAccessory1,1": return "HomePod" 79 | case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" 80 | default: return identifier 81 | } 82 | #elseif os(tvOS) 83 | switch identifier { 84 | case "AppleTV5,3": return "Apple TV 4" 85 | case "AppleTV6,2": return "Apple TV 4K" 86 | case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" 87 | default: return identifier 88 | } 89 | #endif 90 | } 91 | 92 | return mapToDevice(identifier: identifier) 93 | }() 94 | 95 | } 96 | --------------------------------------------------------------------------------