├── .gitignore ├── .hound.yml ├── .swift-version ├── .swiftlint.yml ├── Ajimi.podspec ├── Ajimi.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── Ajimi.xcscheme ├── Ajimi.xcworkspace └── contents.xcworkspacedata ├── Ajimi ├── Ajimi.h └── Info.plist ├── AjimiTests ├── AjimiTests.swift └── Info.plist ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── Example.xcscheme └── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── DetailViewController.swift │ ├── Info.plist │ └── MasterViewController.swift ├── LICENSE ├── README.md ├── Source ├── Assets │ ├── camera.png │ ├── camera@2x.png │ ├── camera@3x.png │ ├── video.png │ ├── video@2x.png │ └── video@3x.png └── Classes │ ├── Ajimi.swift │ ├── Device.swift │ ├── Error.swift │ ├── ImageEditViewController.swift │ ├── ImageUploder.swift │ ├── PostGIFViewController.swift │ ├── PostViewController.swift │ ├── RecordButton.swift │ ├── RectangleView.swift │ ├── Reporter.swift │ ├── ScreenRecorder.swift │ ├── String+Ajimi.swift │ ├── TopViewController.swift │ ├── UIImage+Ajimi.swift │ └── Window.swift ├── demo_snapshot.gif └── demo_video.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Carthage 26 | Carthage/Build 27 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swift: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - cyclomatic_complexity 3 | - file_length 4 | - function_body_length 5 | - function_parameter_count 6 | - opening_brace 7 | - nesting 8 | - type_body_length 9 | - variable_name 10 | - comma 11 | - colon 12 | excluded: 13 | - Carthage 14 | - Pods 15 | colon: 16 | flexible_right_spacing: true 17 | line_length: 250 18 | -------------------------------------------------------------------------------- /Ajimi.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Ajimi.podspec' to ensure this is a 3 | # valid spec and remove all comments before submitting the spec. 4 | # 5 | # Any lines starting with a # are optional, but encouraged 6 | # 7 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 8 | # 9 | 10 | Pod::Spec.new do |s| 11 | s.name = "Ajimi" 12 | s.version = "0.10.3" 13 | s.summary = "Ajimi means tasting. In Japanese, 味見. Ajimi is the feedback tool, which anyone can easily feedback to project team any time." 14 | s.homepage = "https://github.com/nakajijapan/Ajimi" 15 | s.license = 'MIT' 16 | s.author = { "nakajijapan" => "pp.kupepo.gattyanmo@gmail.com" } 17 | s.source = { :git => "https://github.com/nakajijapan/Ajimi.git", :tag => s.version.to_s } 18 | s.social_media_url = 'https://twitter.com/nakajijapan' 19 | 20 | s.platform = :ios, '9.0' 21 | s.requires_arc = true 22 | 23 | s.source_files = 'Source/Classes/**/*' 24 | s.resource_bundles = { 25 | 'Ajimi' => ['Source/Assets/*.png'] 26 | } 27 | 28 | end 29 | -------------------------------------------------------------------------------- /Ajimi.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B08117C01F23B87B00667CD9 /* UIImage+Ajimi.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08117BF1F23B87B00667CD9 /* UIImage+Ajimi.swift */; }; 11 | B08117C31F247DB800667CD9 /* RecordButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08117C21F247DB800667CD9 /* RecordButton.swift */; }; 12 | B0C434061F1E64AD00E5CEE2 /* Ajimi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0C433FC1F1E64AD00E5CEE2 /* Ajimi.framework */; }; 13 | B0C4340B1F1E64AD00E5CEE2 /* AjimiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C4340A1F1E64AD00E5CEE2 /* AjimiTests.swift */; }; 14 | B0C4340D1F1E64AD00E5CEE2 /* Ajimi.h in Headers */ = {isa = PBXBuildFile; fileRef = B0C433FF1F1E64AD00E5CEE2 /* Ajimi.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | B0C4344C1F1E672E00E5CEE2 /* Device.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C4343F1F1E672E00E5CEE2 /* Device.swift */; }; 16 | B0C4344D1F1E672E00E5CEE2 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434401F1E672E00E5CEE2 /* Error.swift */; }; 17 | B0C4344E1F1E672E00E5CEE2 /* ImageEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434411F1E672E00E5CEE2 /* ImageEditViewController.swift */; }; 18 | B0C4344F1F1E672E00E5CEE2 /* Ajimi.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434421F1E672E00E5CEE2 /* Ajimi.swift */; }; 19 | B0C434501F1E672E00E5CEE2 /* PostGIFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434431F1E672E00E5CEE2 /* PostGIFViewController.swift */; }; 20 | B0C434511F1E672E00E5CEE2 /* PostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434441F1E672E00E5CEE2 /* PostViewController.swift */; }; 21 | B0C434521F1E672E00E5CEE2 /* Reporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434451F1E672E00E5CEE2 /* Reporter.swift */; }; 22 | B0C434531F1E672E00E5CEE2 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434461F1E672E00E5CEE2 /* ScreenRecorder.swift */; }; 23 | B0C434541F1E672E00E5CEE2 /* TopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434471F1E672E00E5CEE2 /* TopViewController.swift */; }; 24 | B0C434551F1E672E00E5CEE2 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434481F1E672E00E5CEE2 /* Window.swift */; }; 25 | B0C434561F1E672E00E5CEE2 /* ImageUploder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434491F1E672E00E5CEE2 /* ImageUploder.swift */; }; 26 | B0C434571F1E672E00E5CEE2 /* RectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C4344A1F1E672E00E5CEE2 /* RectangleView.swift */; }; 27 | B0C434581F1E672E00E5CEE2 /* String+Ajimi.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C4344B1F1E672E00E5CEE2 /* String+Ajimi.swift */; }; 28 | B0C434631F1E727100E5CEE2 /* camera@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C434611F1E727100E5CEE2 /* camera@3x.png */; }; 29 | B0C434641F1E727100E5CEE2 /* video@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C434621F1E727100E5CEE2 /* video@3x.png */; }; 30 | B0C434671F1E734300E5CEE2 /* video.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C434651F1E734300E5CEE2 /* video.png */; }; 31 | B0C4346C1F1E736F00E5CEE2 /* camera.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C434691F1E736F00E5CEE2 /* camera.png */; }; 32 | B0C4346D1F1E736F00E5CEE2 /* camera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C4346A1F1E736F00E5CEE2 /* camera@2x.png */; }; 33 | B0C4346E1F1E736F00E5CEE2 /* video@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B0C4346B1F1E736F00E5CEE2 /* video@2x.png */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXContainerItemProxy section */ 37 | B0C434071F1E64AD00E5CEE2 /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = B0C433F31F1E64AD00E5CEE2 /* Project object */; 40 | proxyType = 1; 41 | remoteGlobalIDString = B0C433FB1F1E64AD00E5CEE2; 42 | remoteInfo = Ajimi; 43 | }; 44 | /* End PBXContainerItemProxy section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | B08117BF1F23B87B00667CD9 /* UIImage+Ajimi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Ajimi.swift"; sourceTree = ""; }; 48 | B08117C21F247DB800667CD9 /* RecordButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordButton.swift; sourceTree = ""; }; 49 | B0C433FC1F1E64AD00E5CEE2 /* Ajimi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Ajimi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | B0C433FF1F1E64AD00E5CEE2 /* Ajimi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Ajimi.h; sourceTree = ""; }; 51 | B0C434001F1E64AD00E5CEE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | B0C434051F1E64AD00E5CEE2 /* AjimiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AjimiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | B0C4340A1F1E64AD00E5CEE2 /* AjimiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AjimiTests.swift; sourceTree = ""; }; 54 | B0C4340C1F1E64AD00E5CEE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | B0C4343F1F1E672E00E5CEE2 /* Device.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = ""; }; 56 | B0C434401F1E672E00E5CEE2 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 57 | B0C434411F1E672E00E5CEE2 /* ImageEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditViewController.swift; sourceTree = ""; }; 58 | B0C434421F1E672E00E5CEE2 /* Ajimi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ajimi.swift; sourceTree = ""; }; 59 | B0C434431F1E672E00E5CEE2 /* PostGIFViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostGIFViewController.swift; sourceTree = ""; }; 60 | B0C434441F1E672E00E5CEE2 /* PostViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostViewController.swift; sourceTree = ""; }; 61 | B0C434451F1E672E00E5CEE2 /* Reporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporter.swift; sourceTree = ""; }; 62 | B0C434461F1E672E00E5CEE2 /* ScreenRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = ""; }; 63 | B0C434471F1E672E00E5CEE2 /* TopViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopViewController.swift; sourceTree = ""; }; 64 | B0C434481F1E672E00E5CEE2 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; 65 | B0C434491F1E672E00E5CEE2 /* ImageUploder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageUploder.swift; sourceTree = ""; }; 66 | B0C4344A1F1E672E00E5CEE2 /* RectangleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectangleView.swift; sourceTree = ""; }; 67 | B0C4344B1F1E672E00E5CEE2 /* String+Ajimi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Ajimi.swift"; sourceTree = ""; }; 68 | B0C434611F1E727100E5CEE2 /* camera@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera@3x.png"; sourceTree = ""; }; 69 | B0C434621F1E727100E5CEE2 /* video@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "video@3x.png"; sourceTree = ""; }; 70 | B0C434651F1E734300E5CEE2 /* video.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = video.png; sourceTree = ""; }; 71 | B0C434691F1E736F00E5CEE2 /* camera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = camera.png; sourceTree = ""; }; 72 | B0C4346A1F1E736F00E5CEE2 /* camera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera@2x.png"; sourceTree = ""; }; 73 | B0C4346B1F1E736F00E5CEE2 /* video@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "video@2x.png"; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | B0C433F81F1E64AD00E5CEE2 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | B0C434021F1E64AD00E5CEE2 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | B0C434061F1E64AD00E5CEE2 /* Ajimi.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | B0C433F21F1E64AD00E5CEE2 = { 96 | isa = PBXGroup; 97 | children = ( 98 | B0C4343C1F1E66AB00E5CEE2 /* Source */, 99 | B0C433FE1F1E64AD00E5CEE2 /* Ajimi */, 100 | B0C434091F1E64AD00E5CEE2 /* AjimiTests */, 101 | B0C433FD1F1E64AD00E5CEE2 /* Products */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | B0C433FD1F1E64AD00E5CEE2 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | B0C433FC1F1E64AD00E5CEE2 /* Ajimi.framework */, 109 | B0C434051F1E64AD00E5CEE2 /* AjimiTests.xctest */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | B0C433FE1F1E64AD00E5CEE2 /* Ajimi */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | B0C433FF1F1E64AD00E5CEE2 /* Ajimi.h */, 118 | B0C434001F1E64AD00E5CEE2 /* Info.plist */, 119 | ); 120 | path = Ajimi; 121 | sourceTree = ""; 122 | }; 123 | B0C434091F1E64AD00E5CEE2 /* AjimiTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | B0C4340A1F1E64AD00E5CEE2 /* AjimiTests.swift */, 127 | B0C4340C1F1E64AD00E5CEE2 /* Info.plist */, 128 | ); 129 | path = AjimiTests; 130 | sourceTree = ""; 131 | }; 132 | B0C4343C1F1E66AB00E5CEE2 /* Source */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | B0C4343D1F1E66AB00E5CEE2 /* Assets */, 136 | B0C4343E1F1E66AB00E5CEE2 /* Classes */, 137 | ); 138 | path = Source; 139 | sourceTree = ""; 140 | }; 141 | B0C4343D1F1E66AB00E5CEE2 /* Assets */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | B0C434611F1E727100E5CEE2 /* camera@3x.png */, 145 | B0C434691F1E736F00E5CEE2 /* camera.png */, 146 | B0C4346A1F1E736F00E5CEE2 /* camera@2x.png */, 147 | B0C4346B1F1E736F00E5CEE2 /* video@2x.png */, 148 | B0C434621F1E727100E5CEE2 /* video@3x.png */, 149 | B0C434651F1E734300E5CEE2 /* video.png */, 150 | ); 151 | path = Assets; 152 | sourceTree = ""; 153 | }; 154 | B0C4343E1F1E66AB00E5CEE2 /* Classes */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | B0C4343F1F1E672E00E5CEE2 /* Device.swift */, 158 | B0C434401F1E672E00E5CEE2 /* Error.swift */, 159 | B0C434421F1E672E00E5CEE2 /* Ajimi.swift */, 160 | B0C434411F1E672E00E5CEE2 /* ImageEditViewController.swift */, 161 | B0C434441F1E672E00E5CEE2 /* PostViewController.swift */, 162 | B0C434431F1E672E00E5CEE2 /* PostGIFViewController.swift */, 163 | B0C434451F1E672E00E5CEE2 /* Reporter.swift */, 164 | B0C434461F1E672E00E5CEE2 /* ScreenRecorder.swift */, 165 | B0C434471F1E672E00E5CEE2 /* TopViewController.swift */, 166 | B0C434481F1E672E00E5CEE2 /* Window.swift */, 167 | B08117C21F247DB800667CD9 /* RecordButton.swift */, 168 | B0C434491F1E672E00E5CEE2 /* ImageUploder.swift */, 169 | B0C4344A1F1E672E00E5CEE2 /* RectangleView.swift */, 170 | B0C4344B1F1E672E00E5CEE2 /* String+Ajimi.swift */, 171 | B08117BF1F23B87B00667CD9 /* UIImage+Ajimi.swift */, 172 | ); 173 | path = Classes; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXHeadersBuildPhase section */ 179 | B0C433F91F1E64AD00E5CEE2 /* Headers */ = { 180 | isa = PBXHeadersBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | B0C4340D1F1E64AD00E5CEE2 /* Ajimi.h in Headers */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | /* End PBXHeadersBuildPhase section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | B0C433FB1F1E64AD00E5CEE2 /* Ajimi */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = B0C434101F1E64AD00E5CEE2 /* Build configuration list for PBXNativeTarget "Ajimi" */; 193 | buildPhases = ( 194 | B0C433F71F1E64AD00E5CEE2 /* Sources */, 195 | B0C433F81F1E64AD00E5CEE2 /* Frameworks */, 196 | B0C433F91F1E64AD00E5CEE2 /* Headers */, 197 | B0C433FA1F1E64AD00E5CEE2 /* Resources */, 198 | B08117C11F23B9F400667CD9 /* ShellScript */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | ); 204 | name = Ajimi; 205 | productName = Ajimi; 206 | productReference = B0C433FC1F1E64AD00E5CEE2 /* Ajimi.framework */; 207 | productType = "com.apple.product-type.framework"; 208 | }; 209 | B0C434041F1E64AD00E5CEE2 /* AjimiTests */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = B0C434131F1E64AD00E5CEE2 /* Build configuration list for PBXNativeTarget "AjimiTests" */; 212 | buildPhases = ( 213 | B0C434011F1E64AD00E5CEE2 /* Sources */, 214 | B0C434021F1E64AD00E5CEE2 /* Frameworks */, 215 | B0C434031F1E64AD00E5CEE2 /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | B0C434081F1E64AD00E5CEE2 /* PBXTargetDependency */, 221 | ); 222 | name = AjimiTests; 223 | productName = AjimiTests; 224 | productReference = B0C434051F1E64AD00E5CEE2 /* AjimiTests.xctest */; 225 | productType = "com.apple.product-type.bundle.unit-test"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | B0C433F31F1E64AD00E5CEE2 /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | LastSwiftUpdateCheck = 0830; 234 | LastUpgradeCheck = 0830; 235 | ORGANIZATIONNAME = nakajijapan; 236 | TargetAttributes = { 237 | B0C433FB1F1E64AD00E5CEE2 = { 238 | CreatedOnToolsVersion = 8.3.3; 239 | DevelopmentTeam = CNE5C4C374; 240 | LastSwiftMigration = 0830; 241 | ProvisioningStyle = Automatic; 242 | }; 243 | B0C434041F1E64AD00E5CEE2 = { 244 | CreatedOnToolsVersion = 8.3.3; 245 | DevelopmentTeam = CNE5C4C374; 246 | ProvisioningStyle = Automatic; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = B0C433F61F1E64AD00E5CEE2 /* Build configuration list for PBXProject "Ajimi" */; 251 | compatibilityVersion = "Xcode 3.2"; 252 | developmentRegion = English; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | ); 257 | mainGroup = B0C433F21F1E64AD00E5CEE2; 258 | productRefGroup = B0C433FD1F1E64AD00E5CEE2 /* Products */; 259 | projectDirPath = ""; 260 | projectRoot = ""; 261 | targets = ( 262 | B0C433FB1F1E64AD00E5CEE2 /* Ajimi */, 263 | B0C434041F1E64AD00E5CEE2 /* AjimiTests */, 264 | ); 265 | }; 266 | /* End PBXProject section */ 267 | 268 | /* Begin PBXResourcesBuildPhase section */ 269 | B0C433FA1F1E64AD00E5CEE2 /* Resources */ = { 270 | isa = PBXResourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | B0C434631F1E727100E5CEE2 /* camera@3x.png in Resources */, 274 | B0C4346D1F1E736F00E5CEE2 /* camera@2x.png in Resources */, 275 | B0C4346E1F1E736F00E5CEE2 /* video@2x.png in Resources */, 276 | B0C434671F1E734300E5CEE2 /* video.png in Resources */, 277 | B0C4346C1F1E736F00E5CEE2 /* camera.png in Resources */, 278 | B0C434641F1E727100E5CEE2 /* video@3x.png in Resources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | B0C434031F1E64AD00E5CEE2 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXResourcesBuildPhase section */ 290 | 291 | /* Begin PBXShellScriptBuildPhase section */ 292 | B08117C11F23B9F400667CD9 /* ShellScript */ = { 293 | isa = PBXShellScriptBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | ); 297 | inputPaths = ( 298 | ); 299 | outputPaths = ( 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | shellPath = /bin/sh; 303 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 304 | }; 305 | /* End PBXShellScriptBuildPhase section */ 306 | 307 | /* Begin PBXSourcesBuildPhase section */ 308 | B0C433F71F1E64AD00E5CEE2 /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | B0C434511F1E672E00E5CEE2 /* PostViewController.swift in Sources */, 313 | B0C434561F1E672E00E5CEE2 /* ImageUploder.swift in Sources */, 314 | B0C434571F1E672E00E5CEE2 /* RectangleView.swift in Sources */, 315 | B0C4344D1F1E672E00E5CEE2 /* Error.swift in Sources */, 316 | B0C434531F1E672E00E5CEE2 /* ScreenRecorder.swift in Sources */, 317 | B0C4344C1F1E672E00E5CEE2 /* Device.swift in Sources */, 318 | B0C434501F1E672E00E5CEE2 /* PostGIFViewController.swift in Sources */, 319 | B0C434581F1E672E00E5CEE2 /* String+Ajimi.swift in Sources */, 320 | B08117C01F23B87B00667CD9 /* UIImage+Ajimi.swift in Sources */, 321 | B0C434521F1E672E00E5CEE2 /* Reporter.swift in Sources */, 322 | B0C4344E1F1E672E00E5CEE2 /* ImageEditViewController.swift in Sources */, 323 | B0C434541F1E672E00E5CEE2 /* TopViewController.swift in Sources */, 324 | B0C434551F1E672E00E5CEE2 /* Window.swift in Sources */, 325 | B0C4344F1F1E672E00E5CEE2 /* Ajimi.swift in Sources */, 326 | B08117C31F247DB800667CD9 /* RecordButton.swift in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | B0C434011F1E64AD00E5CEE2 /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | B0C4340B1F1E64AD00E5CEE2 /* AjimiTests.swift in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | /* End PBXSourcesBuildPhase section */ 339 | 340 | /* Begin PBXTargetDependency section */ 341 | B0C434081F1E64AD00E5CEE2 /* PBXTargetDependency */ = { 342 | isa = PBXTargetDependency; 343 | target = B0C433FB1F1E64AD00E5CEE2 /* Ajimi */; 344 | targetProxy = B0C434071F1E64AD00E5CEE2 /* PBXContainerItemProxy */; 345 | }; 346 | /* End PBXTargetDependency section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | B0C4340E1F1E64AD00E5CEE2 /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_ANALYZER_NONNULL = YES; 354 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 356 | CLANG_CXX_LIBRARY = "libc++"; 357 | CLANG_ENABLE_MODULES = YES; 358 | CLANG_ENABLE_OBJC_ARC = YES; 359 | CLANG_WARN_BOOL_CONVERSION = YES; 360 | CLANG_WARN_CONSTANT_CONVERSION = YES; 361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 363 | CLANG_WARN_EMPTY_BODY = YES; 364 | CLANG_WARN_ENUM_CONVERSION = YES; 365 | CLANG_WARN_INFINITE_RECURSION = YES; 366 | CLANG_WARN_INT_CONVERSION = YES; 367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 368 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 369 | CLANG_WARN_UNREACHABLE_CODE = YES; 370 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 371 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 372 | COPY_PHASE_STRIP = NO; 373 | CURRENT_PROJECT_VERSION = 1; 374 | DEBUG_INFORMATION_FORMAT = dwarf; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | ENABLE_TESTABILITY = YES; 377 | GCC_C_LANGUAGE_STANDARD = gnu99; 378 | GCC_DYNAMIC_NO_PIC = NO; 379 | GCC_NO_COMMON_BLOCKS = YES; 380 | GCC_OPTIMIZATION_LEVEL = 0; 381 | GCC_PREPROCESSOR_DEFINITIONS = ( 382 | "DEBUG=1", 383 | "$(inherited)", 384 | ); 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 392 | MTL_ENABLE_DEBUG_INFO = YES; 393 | ONLY_ACTIVE_ARCH = YES; 394 | SDKROOT = iphoneos; 395 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 396 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 397 | TARGETED_DEVICE_FAMILY = "1,2"; 398 | VERSIONING_SYSTEM = "apple-generic"; 399 | VERSION_INFO_PREFIX = ""; 400 | }; 401 | name = Debug; 402 | }; 403 | B0C4340F1F1E64AD00E5CEE2 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_ANALYZER_NONNULL = YES; 408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BOOL_CONVERSION = YES; 414 | CLANG_WARN_CONSTANT_CONVERSION = YES; 415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 416 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 417 | CLANG_WARN_EMPTY_BODY = YES; 418 | CLANG_WARN_ENUM_CONVERSION = YES; 419 | CLANG_WARN_INFINITE_RECURSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 423 | CLANG_WARN_UNREACHABLE_CODE = YES; 424 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 425 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 426 | COPY_PHASE_STRIP = NO; 427 | CURRENT_PROJECT_VERSION = 1; 428 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 429 | ENABLE_NS_ASSERTIONS = NO; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | GCC_C_LANGUAGE_STANDARD = gnu99; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 440 | MTL_ENABLE_DEBUG_INFO = NO; 441 | SDKROOT = iphoneos; 442 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 443 | TARGETED_DEVICE_FAMILY = "1,2"; 444 | VALIDATE_PRODUCT = YES; 445 | VERSIONING_SYSTEM = "apple-generic"; 446 | VERSION_INFO_PREFIX = ""; 447 | }; 448 | name = Release; 449 | }; 450 | B0C434111F1E64AD00E5CEE2 /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | CLANG_ENABLE_MODULES = YES; 454 | CODE_SIGN_IDENTITY = ""; 455 | DEFINES_MODULE = YES; 456 | DEVELOPMENT_TEAM = CNE5C4C374; 457 | DYLIB_COMPATIBILITY_VERSION = 1; 458 | DYLIB_CURRENT_VERSION = 1; 459 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 460 | INFOPLIST_FILE = Ajimi/Info.plist; 461 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 462 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 464 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.Ajimi; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | SKIP_INSTALL = YES; 467 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 468 | SWIFT_VERSION = 3.0; 469 | }; 470 | name = Debug; 471 | }; 472 | B0C434121F1E64AD00E5CEE2 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | CLANG_ENABLE_MODULES = YES; 476 | CODE_SIGN_IDENTITY = ""; 477 | DEFINES_MODULE = YES; 478 | DEVELOPMENT_TEAM = CNE5C4C374; 479 | DYLIB_COMPATIBILITY_VERSION = 1; 480 | DYLIB_CURRENT_VERSION = 1; 481 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 482 | INFOPLIST_FILE = Ajimi/Info.plist; 483 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 484 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.Ajimi; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SKIP_INSTALL = YES; 489 | SWIFT_VERSION = 3.0; 490 | }; 491 | name = Release; 492 | }; 493 | B0C434141F1E64AD00E5CEE2 /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 497 | DEVELOPMENT_TEAM = CNE5C4C374; 498 | INFOPLIST_FILE = AjimiTests/Info.plist; 499 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 500 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.AjimiTests; 501 | PRODUCT_NAME = "$(TARGET_NAME)"; 502 | SWIFT_VERSION = 3.0; 503 | }; 504 | name = Debug; 505 | }; 506 | B0C434151F1E64AD00E5CEE2 /* Release */ = { 507 | isa = XCBuildConfiguration; 508 | buildSettings = { 509 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 510 | DEVELOPMENT_TEAM = CNE5C4C374; 511 | INFOPLIST_FILE = AjimiTests/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 513 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.AjimiTests; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | SWIFT_VERSION = 3.0; 516 | }; 517 | name = Release; 518 | }; 519 | /* End XCBuildConfiguration section */ 520 | 521 | /* Begin XCConfigurationList section */ 522 | B0C433F61F1E64AD00E5CEE2 /* Build configuration list for PBXProject "Ajimi" */ = { 523 | isa = XCConfigurationList; 524 | buildConfigurations = ( 525 | B0C4340E1F1E64AD00E5CEE2 /* Debug */, 526 | B0C4340F1F1E64AD00E5CEE2 /* Release */, 527 | ); 528 | defaultConfigurationIsVisible = 0; 529 | defaultConfigurationName = Release; 530 | }; 531 | B0C434101F1E64AD00E5CEE2 /* Build configuration list for PBXNativeTarget "Ajimi" */ = { 532 | isa = XCConfigurationList; 533 | buildConfigurations = ( 534 | B0C434111F1E64AD00E5CEE2 /* Debug */, 535 | B0C434121F1E64AD00E5CEE2 /* Release */, 536 | ); 537 | defaultConfigurationIsVisible = 0; 538 | defaultConfigurationName = Release; 539 | }; 540 | B0C434131F1E64AD00E5CEE2 /* Build configuration list for PBXNativeTarget "AjimiTests" */ = { 541 | isa = XCConfigurationList; 542 | buildConfigurations = ( 543 | B0C434141F1E64AD00E5CEE2 /* Debug */, 544 | B0C434151F1E64AD00E5CEE2 /* Release */, 545 | ); 546 | defaultConfigurationIsVisible = 0; 547 | defaultConfigurationName = Release; 548 | }; 549 | /* End XCConfigurationList section */ 550 | }; 551 | rootObject = B0C433F31F1E64AD00E5CEE2 /* Project object */; 552 | } 553 | -------------------------------------------------------------------------------- /Ajimi.xcodeproj/xcshareddata/xcschemes/Ajimi.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Ajimi.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Ajimi/Ajimi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Ajimi.h 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/19. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Ajimi. 12 | FOUNDATION_EXPORT double AjimiVersionNumber; 13 | 14 | //! Project version string for Ajimi. 15 | FOUNDATION_EXPORT const unsigned char AjimiVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Ajimi/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /AjimiTests/AjimiTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AjimiTests.swift 3 | // AjimiTests 4 | // 5 | // Created by nakajijapan on 2017/07/19. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Ajimi 11 | 12 | class AjimiTests: XCTestCase { 13 | func test64() { 14 | let string = "nakajijapan:nakaji" 15 | let authString = string.base64 16 | XCTAssertEqual(authString, "bmFrYWppamFwYW46bmFrYWpp") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AjimiTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B0C434231F1E64ED00E5CEE2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434221F1E64ED00E5CEE2 /* AppDelegate.swift */; }; 11 | B0C434251F1E64ED00E5CEE2 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434241F1E64ED00E5CEE2 /* MasterViewController.swift */; }; 12 | B0C434271F1E64ED00E5CEE2 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C434261F1E64ED00E5CEE2 /* DetailViewController.swift */; }; 13 | B0C4342A1F1E64ED00E5CEE2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0C434281F1E64ED00E5CEE2 /* Main.storyboard */; }; 14 | B0C4342C1F1E64ED00E5CEE2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B0C4342B1F1E64ED00E5CEE2 /* Assets.xcassets */; }; 15 | B0C4342F1F1E64ED00E5CEE2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0C4342D1F1E64ED00E5CEE2 /* LaunchScreen.storyboard */; }; 16 | B0C4345A1F1E6FCB00E5CEE2 /* Ajimi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0C434591F1E6FCB00E5CEE2 /* Ajimi.framework */; }; 17 | B0C4345B1F1E6FCB00E5CEE2 /* Ajimi.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B0C434591F1E6FCB00E5CEE2 /* Ajimi.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | B0DB450F1F26F536000F0D84 /* Ajimi.podspec in Resources */ = {isa = PBXBuildFile; fileRef = B0DB450D1F26F536000F0D84 /* Ajimi.podspec */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | B0C4345C1F1E6FCC00E5CEE2 /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | B0C4345B1F1E6FCB00E5CEE2 /* Ajimi.framework in Embed Frameworks */, 29 | ); 30 | name = "Embed Frameworks"; 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXCopyFilesBuildPhase section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | B0C4341F1F1E64ED00E5CEE2 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | B0C434221F1E64ED00E5CEE2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | B0C434241F1E64ED00E5CEE2 /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 39 | B0C434261F1E64ED00E5CEE2 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 40 | B0C434291F1E64ED00E5CEE2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | B0C4342B1F1E64ED00E5CEE2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | B0C4342E1F1E64ED00E5CEE2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43 | B0C434301F1E64ED00E5CEE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | B0C434591F1E6FCB00E5CEE2 /* Ajimi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ajimi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | B0DB450D1F26F536000F0D84 /* Ajimi.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Ajimi.podspec; path = ../Ajimi.podspec; sourceTree = ""; }; 46 | B0DB450E1F26F536000F0D84 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | B0C4341C1F1E64ED00E5CEE2 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | B0C4345A1F1E6FCB00E5CEE2 /* Ajimi.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | B0C434161F1E64ED00E5CEE2 = { 62 | isa = PBXGroup; 63 | children = ( 64 | B0DB450C1F26F51B000F0D84 /* Meta */, 65 | B0C434591F1E6FCB00E5CEE2 /* Ajimi.framework */, 66 | B0C434211F1E64ED00E5CEE2 /* Example */, 67 | B0C434201F1E64ED00E5CEE2 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | B0C434201F1E64ED00E5CEE2 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | B0C4341F1F1E64ED00E5CEE2 /* Example.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | B0C434211F1E64ED00E5CEE2 /* Example */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | B0C434221F1E64ED00E5CEE2 /* AppDelegate.swift */, 83 | B0C434241F1E64ED00E5CEE2 /* MasterViewController.swift */, 84 | B0C434261F1E64ED00E5CEE2 /* DetailViewController.swift */, 85 | B0C434281F1E64ED00E5CEE2 /* Main.storyboard */, 86 | B0C4342B1F1E64ED00E5CEE2 /* Assets.xcassets */, 87 | B0C4342D1F1E64ED00E5CEE2 /* LaunchScreen.storyboard */, 88 | B0C434301F1E64ED00E5CEE2 /* Info.plist */, 89 | ); 90 | path = Example; 91 | sourceTree = ""; 92 | }; 93 | B0DB450C1F26F51B000F0D84 /* Meta */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | B0DB450D1F26F536000F0D84 /* Ajimi.podspec */, 97 | B0DB450E1F26F536000F0D84 /* README.md */, 98 | ); 99 | name = Meta; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | B0C4341E1F1E64ED00E5CEE2 /* Example */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = B0C434331F1E64ED00E5CEE2 /* Build configuration list for PBXNativeTarget "Example" */; 108 | buildPhases = ( 109 | B0C4341B1F1E64ED00E5CEE2 /* Sources */, 110 | B0C4341C1F1E64ED00E5CEE2 /* Frameworks */, 111 | B0C4341D1F1E64ED00E5CEE2 /* Resources */, 112 | B0C4345C1F1E6FCC00E5CEE2 /* Embed Frameworks */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = Example; 119 | productName = Example; 120 | productReference = B0C4341F1F1E64ED00E5CEE2 /* Example.app */; 121 | productType = "com.apple.product-type.application"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | B0C434171F1E64ED00E5CEE2 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastSwiftUpdateCheck = 0830; 130 | LastUpgradeCheck = 0830; 131 | ORGANIZATIONNAME = nakajijapan; 132 | TargetAttributes = { 133 | B0C4341E1F1E64ED00E5CEE2 = { 134 | CreatedOnToolsVersion = 8.3.3; 135 | DevelopmentTeam = CNE5C4C374; 136 | ProvisioningStyle = Automatic; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = B0C4341A1F1E64ED00E5CEE2 /* Build configuration list for PBXProject "Example" */; 141 | compatibilityVersion = "Xcode 3.2"; 142 | developmentRegion = English; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = B0C434161F1E64ED00E5CEE2; 149 | productRefGroup = B0C434201F1E64ED00E5CEE2 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | B0C4341E1F1E64ED00E5CEE2 /* Example */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | B0C4341D1F1E64ED00E5CEE2 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | B0C4342F1F1E64ED00E5CEE2 /* LaunchScreen.storyboard in Resources */, 164 | B0C4342C1F1E64ED00E5CEE2 /* Assets.xcassets in Resources */, 165 | B0C4342A1F1E64ED00E5CEE2 /* Main.storyboard in Resources */, 166 | B0DB450F1F26F536000F0D84 /* Ajimi.podspec in Resources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXResourcesBuildPhase section */ 171 | 172 | /* Begin PBXSourcesBuildPhase section */ 173 | B0C4341B1F1E64ED00E5CEE2 /* Sources */ = { 174 | isa = PBXSourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | B0C434271F1E64ED00E5CEE2 /* DetailViewController.swift in Sources */, 178 | B0C434251F1E64ED00E5CEE2 /* MasterViewController.swift in Sources */, 179 | B0C434231F1E64ED00E5CEE2 /* AppDelegate.swift in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | B0C434281F1E64ED00E5CEE2 /* Main.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | B0C434291F1E64ED00E5CEE2 /* Base */, 190 | ); 191 | name = Main.storyboard; 192 | sourceTree = ""; 193 | }; 194 | B0C4342D1F1E64ED00E5CEE2 /* LaunchScreen.storyboard */ = { 195 | isa = PBXVariantGroup; 196 | children = ( 197 | B0C4342E1F1E64ED00E5CEE2 /* Base */, 198 | ); 199 | name = LaunchScreen.storyboard; 200 | sourceTree = ""; 201 | }; 202 | /* End PBXVariantGroup section */ 203 | 204 | /* Begin XCBuildConfiguration section */ 205 | B0C434311F1E64ED00E5CEE2 /* Debug */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 247 | MTL_ENABLE_DEBUG_INFO = YES; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 251 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 252 | TARGETED_DEVICE_FAMILY = "1,2"; 253 | }; 254 | name = Debug; 255 | }; 256 | B0C434321F1E64ED00E5CEE2 /* Release */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_ANALYZER_NONNULL = YES; 261 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 263 | CLANG_CXX_LIBRARY = "libc++"; 264 | CLANG_ENABLE_MODULES = YES; 265 | CLANG_ENABLE_OBJC_ARC = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | ENABLE_NS_ASSERTIONS = NO; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 292 | MTL_ENABLE_DEBUG_INFO = NO; 293 | SDKROOT = iphoneos; 294 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 295 | TARGETED_DEVICE_FAMILY = "1,2"; 296 | VALIDATE_PRODUCT = YES; 297 | }; 298 | name = Release; 299 | }; 300 | B0C434341F1E64ED00E5CEE2 /* Debug */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 304 | DEVELOPMENT_TEAM = CNE5C4C374; 305 | INFOPLIST_FILE = Example/Info.plist; 306 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 307 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 308 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.Example; 309 | PRODUCT_NAME = "$(TARGET_NAME)"; 310 | SWIFT_VERSION = 3.0; 311 | }; 312 | name = Debug; 313 | }; 314 | B0C434351F1E64ED00E5CEE2 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | DEVELOPMENT_TEAM = CNE5C4C374; 319 | INFOPLIST_FILE = Example/Info.plist; 320 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 321 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 322 | PRODUCT_BUNDLE_IDENTIFIER = nakajijapan.net.Example; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 3.0; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | B0C4341A1F1E64ED00E5CEE2 /* Build configuration list for PBXProject "Example" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | B0C434311F1E64ED00E5CEE2 /* Debug */, 335 | B0C434321F1E64ED00E5CEE2 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | B0C434331F1E64ED00E5CEE2 /* Build configuration list for PBXNativeTarget "Example" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | B0C434341F1E64ED00E5CEE2 /* Debug */, 344 | B0C434351F1E64ED00E5CEE2 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = B0C434171F1E64ED00E5CEE2 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by nakajijapan on 2017/07/19. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Ajimi 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | #if DEBUG 18 | let ajimiOptions = AjimiOptions( 19 | githubBasePath: "https://hostname/api/v3", 20 | githubAccessToken: "githubAccessToken", 21 | githubUser: "userName", 22 | githubRepo: "repositoryName", 23 | imageUploadURL: URL(string: "https://hostname/image/upload")!, 24 | imageUploadKey: "imageUploadKey" 25 | ) 26 | #endif 27 | 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 29 | 30 | guard let window = window, let splitViewController = window.rootViewController as? UISplitViewController else { 31 | return false 32 | } 33 | 34 | guard let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as? UINavigationController else { 35 | return false 36 | } 37 | navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem 38 | splitViewController.delegate = self 39 | 40 | #if DEBUG 41 | Ajimi.show(ajimiOptions) 42 | #endif 43 | 44 | return true 45 | } 46 | 47 | func applicationWillResignActive(_ application: UIApplication) { 48 | } 49 | 50 | func applicationDidEnterBackground(_ application: UIApplication) { 51 | } 52 | 53 | func applicationWillEnterForeground(_ application: UIApplication) { 54 | } 55 | 56 | func applicationDidBecomeActive(_ application: UIApplication) { 57 | } 58 | 59 | func applicationWillTerminate(_ application: UIApplication) { 60 | } 61 | 62 | // MARK: - Split view 63 | 64 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { 65 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 66 | guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } 67 | if topAsDetailController.detailItem == nil { 68 | return true 69 | } 70 | return false 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /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 | 27 | 28 | -------------------------------------------------------------------------------- /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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /Example/Example/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // Example 4 | // 5 | // Created by nakajijapan on 2017/07/19. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | @IBOutlet weak var detailDescriptionLabel: UILabel! 14 | 15 | func configureView() { 16 | if let detail = detailItem { 17 | if let label = detailDescriptionLabel { 18 | label.text = detail.description 19 | } 20 | } 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | configureView() 26 | } 27 | 28 | var detailItem: NSDate? { 29 | didSet { 30 | configureView() 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | UIStatusBarTintParameters 32 | 33 | UINavigationBar 34 | 35 | Style 36 | UIBarStyleDefault 37 | Translucent 38 | 39 | 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Example/Example/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // Example 4 | // 5 | // Created by nakajijapan on 2017/07/19. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Ajimi 11 | 12 | class MasterViewController: UITableViewController { 13 | 14 | var detailViewController: DetailViewController? 15 | var objects = [Any]() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | navigationItem.leftBarButtonItem = editButtonItem 20 | 21 | let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:))) 22 | let hideAjimiButton = UIBarButtonItem(title: "hide", style: .plain, target: self, action: #selector(hideAjimi(_:))) 23 | let showAjimiButton = UIBarButtonItem(title: "show", style: .plain, target: self, action: #selector(showAjimi(_:))) 24 | 25 | navigationItem.rightBarButtonItems = [addButton, showAjimiButton, hideAjimiButton] 26 | 27 | if let split = splitViewController { 28 | let controllers = split.viewControllers 29 | 30 | guard let navigationController = controllers[controllers.count-1] as? UINavigationController else { 31 | return 32 | } 33 | 34 | detailViewController = navigationController.topViewController as? DetailViewController 35 | } 36 | } 37 | 38 | override func viewWillAppear(_ animated: Bool) { 39 | clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed 40 | super.viewWillAppear(animated) 41 | } 42 | 43 | func insertNewObject(_ sender: Any) { 44 | objects.insert(NSDate(), at: 0) 45 | let indexPath = IndexPath(row: 0, section: 0) 46 | tableView.insertRows(at: [indexPath], with: .automatic) 47 | } 48 | 49 | func hideAjimi(_ sender: Any) { 50 | Ajimi.hide() 51 | } 52 | 53 | func showAjimi(_ sender: Any) { 54 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 55 | Ajimi.show(appDelegate.ajimiOptions) 56 | } 57 | } 58 | 59 | // MARK: - Segues 60 | 61 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 62 | if segue.identifier == "showDetail" { 63 | if let indexPath = tableView.indexPathForSelectedRow { 64 | guard let object = objects[indexPath.row] as? NSDate else { return } 65 | guard let controller = (segue.destination as? UINavigationController)?.topViewController as? DetailViewController else { return } 66 | controller.detailItem = object 67 | controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem 68 | controller.navigationItem.leftItemsSupplementBackButton = true 69 | } 70 | } 71 | } 72 | 73 | // MARK: - Table View 74 | 75 | override func numberOfSections(in tableView: UITableView) -> Int { 76 | return 1 77 | } 78 | 79 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 80 | return objects.count 81 | } 82 | 83 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 84 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 85 | guard let object = objects[indexPath.row] as? NSDate else { 86 | fatalError("invalid object data") 87 | } 88 | cell.textLabel!.text = object.description 89 | return cell 90 | } 91 | 92 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 93 | // Return false if you do not want the specified item to be editable. 94 | return true 95 | } 96 | 97 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 98 | if editingStyle == .delete { 99 | objects.remove(at: indexPath.row) 100 | tableView.deleteRows(at: [indexPath], with: .fade) 101 | } else if editingStyle == .insert { 102 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 nakajijapan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all 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 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ajimi 2 | 3 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![Version](https://img.shields.io/cocoapods/v/Ajimi.svg?style=flat)](http://cocoapods.org/pods/Ajimi) 5 | [![License](https://img.shields.io/cocoapods/l/Ajimi.svg?style=flat)](http://cocoapods.org/pods/Ajimi) 6 | [![Platform](https://img.shields.io/cocoapods/p/Ajimi.svg?style=flat)](http://cocoapods.org/pods/Ajimi) 7 | [![Language](https://img.shields.io/badge/language-Swift%203-orange.svg)](https://swift.org) 8 | 9 | 10 | Ajimi means tasting. In Japanese, 味見. 11 | Ajimi is the feedback tool, which anyone can easily feedback to project team any time. 12 | 13 | Snapshot | Video 14 | ---- | ---- 15 | ![](demo_snapshot.gif) | ![](demo_video.gif) 16 | 17 | ## Requirements 18 | 19 | - Xcode 8+ 20 | - Swift 3.0+ 21 | - iOS 9+ 22 | 23 | ## Installation 24 | 25 | ### CocoaPods 26 | 27 | Ajimi is available through [CocoaPods](http://cocoapods.org). To install 28 | it, simply add the following line to your Podfile: 29 | 30 | ```ruby 31 | pod "Ajimi" 32 | ``` 33 | 34 | ### Carthage 35 | 36 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager for Cocoa application. 37 | 38 | ``` bash 39 | $ brew update 40 | $ brew install carthage 41 | ``` 42 | 43 | To integrate Kingfisher into your Xcode project using Carthage, specify it in your `Cartfile`: 44 | 45 | ``` ogdl 46 | github "nakajijapan/Ajimi" 47 | ``` 48 | 49 | Then, run the following command to build the Kingfisher framework: 50 | 51 | ``` bash 52 | $ carthage update 53 | ``` 54 | 55 | ## Usage 56 | 57 | ### In AppDelegate 58 | 59 | ``` swift 60 | 61 | #if DEBUG 62 | let ajimiOptions = AjimiOptions( 63 | githubBasePath: "https://hostname/api/v3", 64 | githubAccessToken: "tokentoken", 65 | githubUser: "nakajijapan", 66 | githubRepo: "FeedbackTool", 67 | imageUploadURL: URL(string: "https://hostname/image/upload")!, 68 | imageUploadKey: "keykey" 69 | ) 70 | Ajimi.show(ajimiOptions) 71 | #endif 72 | 73 | ``` 74 | 75 | ## Author 76 | 77 | nakajijapan, pp.kupepo.gattyanmo@gmail.com 78 | 79 | ## License 80 | 81 | Ajimi is available under the MIT license. See the LICENSE file for more info. 82 | 83 | ## About Icon 84 |
Icons made by Pixel perfect from www.flaticon.com is licensed by CC 3.0 BY
85 | -------------------------------------------------------------------------------- /Source/Assets/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/camera.png -------------------------------------------------------------------------------- /Source/Assets/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/camera@2x.png -------------------------------------------------------------------------------- /Source/Assets/camera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/camera@3x.png -------------------------------------------------------------------------------- /Source/Assets/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/video.png -------------------------------------------------------------------------------- /Source/Assets/video@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/video@2x.png -------------------------------------------------------------------------------- /Source/Assets/video@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/Source/Assets/video@3x.png -------------------------------------------------------------------------------- /Source/Classes/Ajimi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ajimi.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/11. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct AjimiOptions { 12 | public let imageUploadURL: URL 13 | public let imageUploadKey: String? 14 | public let github: GitHubOptions 15 | 16 | public init(githubBasePath: String, githubAccessToken: String, githubUser: String, githubRepo: String, imageUploadURL: URL, imageUploadKey: String?) { 17 | 18 | github = GitHubOptions( 19 | basePath: githubBasePath, 20 | accessToken: githubAccessToken, 21 | user: githubUser, 22 | repo: githubRepo 23 | ) 24 | 25 | self.imageUploadURL = imageUploadURL 26 | self.imageUploadKey = imageUploadKey 27 | } 28 | 29 | public struct GitHubOptions { 30 | let basePath: String 31 | let accessToken: String 32 | let user: String 33 | let repo: String 34 | } 35 | 36 | } 37 | 38 | public class Ajimi: NSObject { 39 | 40 | fileprivate static var AssocKeyWindow: UInt8 = 0 41 | 42 | class var targetWindow: UIWindow { 43 | let windows = UIApplication.shared.windows.filter { !($0 is Window) } 44 | guard let window = windows.first else { fatalError("invalid window") } 45 | return window 46 | } 47 | 48 | class var window: Window { 49 | let windows = UIApplication.shared.windows.filter { $0 is Window } 50 | guard let window = windows.first as? Window else { fatalError("invalid window") } 51 | return window 52 | } 53 | 54 | public class func show(_ options: AjimiOptions) { 55 | let application = UIApplication.shared 56 | if objc_getAssociatedObject(application, &Ajimi.AssocKeyWindow) == nil { 57 | 58 | let window = Window(frame: UIScreen.main.bounds) 59 | window.options = options 60 | window.backgroundColor = UIColor.clear 61 | window.rootViewController = TopViewController(nibName: nil, bundle: nil) 62 | window.windowLevel = UIWindowLevelNormal + 20.0 63 | window.makeKeyAndVisible() 64 | 65 | objc_setAssociatedObject( 66 | application, 67 | &Ajimi.AssocKeyWindow, 68 | window, 69 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC 70 | ) 71 | } 72 | } 73 | 74 | public class func hide() { 75 | let application = UIApplication.shared 76 | if let window = objc_getAssociatedObject(application, &Ajimi.AssocKeyWindow) as? UIWindow { 77 | window.rootViewController!.view.removeFromSuperview() 78 | window.rootViewController = nil 79 | 80 | objc_setAssociatedObject( 81 | application, 82 | &Ajimi.AssocKeyWindow, 83 | nil, 84 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC 85 | ) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Source/Classes/Device.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Device.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/16. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SystemConfiguration 12 | 13 | struct Device { 14 | static var all: [String: Any] { 15 | var details: [String: Any] = [:] 16 | 17 | details["iOS Version"] = UIDevice.current.systemVersion 18 | 19 | let width = String(Int(UIScreen.main.bounds.size.width)) 20 | let height = String(Int(UIScreen.main.bounds.size.height)) 21 | details["Device"] = UIDevice.modelName() 22 | details["Screen"] = "\(width) x \(height)" 23 | details["Uptime"] = String(String(Int(ProcessInfo().systemUptime) / 60) + " mins") 24 | 25 | if let timezone = TimeZone.current.abbreviation() { 26 | details["Timezone"] = timezone 27 | } 28 | details["Language"] = NSLocale.preferredLanguages[0] 29 | return details 30 | } 31 | 32 | } 33 | 34 | extension UIDevice { 35 | static func modelName() -> String { 36 | var systemInfo = utsname() 37 | uname(&systemInfo) 38 | let machineMirror = Mirror(reflecting: systemInfo.machine) 39 | let identifier = machineMirror.children.reduce("") { identifier, element in 40 | guard let value = element.value as? Int8, value != 0 else { return identifier } 41 | return identifier + String(UnicodeScalar(UInt8(value))) 42 | } 43 | 44 | return identifier 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/Classes/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/16. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Result { 12 | case success(T) 13 | case failure(Error) 14 | } 15 | 16 | enum ReporterError: Error { 17 | case NetworkError(String) 18 | case GitHubSaveError(String) 19 | } 20 | 21 | enum ImageUploderError: Error { 22 | case RequestParseError() 23 | case UploadError(String) 24 | case URLCreateError() 25 | } 26 | -------------------------------------------------------------------------------- /Source/Classes/ImageEditViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditViewController.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/11. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageEditViewController: UIViewController { 12 | 13 | let image: UIImage 14 | let containerView = UIView(frame: .zero) 15 | 16 | init(image: UIImage) { 17 | self.image = image 18 | super.init(nibName: nil, bundle: nil) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | self.title = "Ajimi" 28 | 29 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(rightBarButtonItemDidTap(_:))) 30 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(leftBarButtonItemDidTap(_:))) 31 | view.backgroundColor = .black 32 | 33 | layoutToolbar() 34 | view.layoutIfNeeded() 35 | 36 | let statusBarHeight = UIApplication.shared.statusBarFrame.size.height 37 | let navigationBarHeight = navigationController?.navigationBar.bounds.height ?? 0.0 38 | let imageViewHeight = view.bounds.height - toolbar.bounds.height - navigationBarHeight - statusBarHeight 39 | let imageViewWidth = (imageViewHeight * image.size.width) / image.size.height 40 | let frame = CGRect(x: 0, y: 0, width: imageViewWidth, height: imageViewHeight) 41 | view.addSubview(containerView) 42 | 43 | containerView.translatesAutoresizingMaskIntoConstraints = false 44 | containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true 45 | containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 46 | containerView.widthAnchor.constraint(equalToConstant: imageViewWidth).isActive = true 47 | containerView.bottomAnchor.constraint(equalTo: toolbar.topAnchor).isActive = true 48 | 49 | let imageView = UIImageView(frame: frame) 50 | imageView.image = image 51 | imageView.contentMode = .scaleAspectFit 52 | containerView.addSubview(imageView) 53 | } 54 | 55 | let toolbar = UIToolbar(frame: .zero) 56 | 57 | func layoutToolbar() { 58 | let rectangleBarButtonItem: UIBarButtonItem = UIBarButtonItem(title: "rectangle", style: .plain, target: self, action: #selector(onClickBarButton(_:))) 59 | rectangleBarButtonItem.tag = 1 60 | 61 | toolbar.items = [rectangleBarButtonItem] 62 | view.addSubview(toolbar) 63 | 64 | toolbar.translatesAutoresizingMaskIntoConstraints = false 65 | toolbar.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 66 | toolbar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 67 | toolbar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 68 | } 69 | 70 | func onClickBarButton(_ button: UIBarButtonItem) { 71 | let side = 100.0 72 | let rectangleView = RectangleView(frame: CGRect(x: 0.0, y: 0.0, width: side, height: side)) 73 | containerView.addSubview(rectangleView) 74 | rectangleViews.append(rectangleView) 75 | } 76 | 77 | var rectangleViews: [RectangleView] = [] 78 | 79 | func rightBarButtonItemDidTap(_ button: UIBarButtonItem) { 80 | 81 | UIGraphicsBeginImageContext(image.size) 82 | guard let context = UIGraphicsGetCurrentContext() else { return } 83 | 84 | image.draw(in: CGRect(origin: .zero, size: image.size)) 85 | let ratio = (image.size.width / containerView.frame.size.width) 86 | 87 | rectangleViews.forEach { rectangleView in 88 | var frame = rectangleView.rectangleRect() 89 | frame.origin.x *= ratio 90 | frame.origin.y *= ratio 91 | frame.size.width *= ratio 92 | frame.size.height *= ratio 93 | 94 | let layer = rectangleView.createShapeLayer() 95 | layer.path = UIBezierPath(rect: frame).cgPath 96 | layer.render(in: context) 97 | } 98 | 99 | let newImage = UIGraphicsGetImageFromCurrentImageContext()! 100 | UIGraphicsEndImageContext() 101 | 102 | let viewController = PostViewController(image: newImage) 103 | navigationController?.pushViewController(viewController, animated: true) 104 | 105 | } 106 | 107 | func leftBarButtonItemDidTap(_ button: UIBarButtonItem) { 108 | dismiss(animated: true) { } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Source/Classes/ImageUploder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageUploder.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/16. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol ImageUploderProtocol: class { 13 | static func uploadImage(url: URL, key: String?, imageData: Data, completion: @escaping (Result) -> Void) 14 | } 15 | 16 | class ImageUploder: ImageUploderProtocol { 17 | 18 | static func uploadImage(url: URL, key: String?, imageData: Data, completion: @escaping (Result) -> Void) { 19 | 20 | let parameters = ["key" : key] 21 | let boundary = "----BOUNDARYBOUNDARY----" 22 | 23 | let body: Data 24 | do { 25 | body = try self.createBody(with: parameters, image: imageData, boundary: boundary) 26 | } catch { 27 | completion(Result.failure(ImageUploderError.RequestParseError())) 28 | return 29 | } 30 | 31 | var request = URLRequest(url: url) 32 | request.httpMethod = "POST" 33 | request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length") 34 | request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") 35 | request.setValue("ios-ajimi/1.0", forHTTPHeaderField: "User-Agent") 36 | request.httpBody = body 37 | 38 | let task = URLSession.shared.dataTask(with: request) { data, _, error in 39 | if error != nil { 40 | completion(Result.failure(ImageUploderError.UploadError(error?.localizedDescription ?? ""))) 41 | return 42 | } 43 | 44 | guard let data = data, let urlString = String(data: data, encoding: .utf8) else { 45 | completion(Result.failure(ImageUploderError.URLCreateError())) 46 | return 47 | } 48 | 49 | completion(Result.success(urlString)) 50 | } 51 | task.resume() 52 | } 53 | 54 | private static func createBody(with parameters: [String: String?], image: Data, boundary: String) throws -> Data { 55 | var body = Data() 56 | parameters.forEach { key, value in 57 | guard let value = value else { return } 58 | body.append("--\(boundary)\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 59 | body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 60 | body.append("\(value)\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 61 | } 62 | body.append("--\(boundary)\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 63 | body.append("Content-Disposition: form-data; name=\"imagedata\"; filename=\"gyazo.com\"\r\n\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 64 | body.append(image) 65 | body.append("\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 66 | body.append("--\(boundary)--\r\n".data(using: String.Encoding.ascii, allowLossyConversion: false)!) 67 | return body 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Source/Classes/PostGIFViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostGIFViewController.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/17. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PostGIFViewController: UIViewController, PostViewControllerProtocol { 12 | 13 | let imageURL: URL 14 | let textField = UITextField(frame: .zero) 15 | let textView = UITextView(frame: .zero) 16 | 17 | init(imageURL: URL) { 18 | self.imageURL = imageURL 19 | super.init(nibName: nil, bundle: nil) 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | self.title = "Ajimi" 29 | 30 | view.backgroundColor = .white 31 | automaticallyAdjustsScrollViewInsets = false 32 | 33 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(leftBarButtonItemDidTap(_:))) 34 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Send", style: UIBarButtonItemStyle.plain, target: self, action: #selector(rightBarButtonItemDidTap(_:))) 35 | 36 | let webView = UIWebView(frame: .zero) 37 | let htmlString = "" 38 | webView.loadHTMLString(htmlString, baseURL: nil) 39 | 40 | view.addSubview(webView) 41 | 42 | webView.translatesAutoresizingMaskIntoConstraints = false 43 | webView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true 44 | webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 45 | webView.widthAnchor.constraint(equalToConstant: 100).isActive = true 46 | webView.heightAnchor.constraint(equalToConstant: 200).isActive = true 47 | webView.backgroundColor = .gray 48 | view.addSubview(textField) 49 | 50 | textField.translatesAutoresizingMaskIntoConstraints = false 51 | textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 64 + 8).isActive = true 52 | textField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true 53 | textField.rightAnchor.constraint(equalTo: webView.leftAnchor, constant: -8).isActive = true 54 | textField.heightAnchor.constraint(equalToConstant: 44).isActive = true 55 | textField.placeholder = "title" 56 | textField.borderStyle = UITextBorderStyle.roundedRect 57 | view.addSubview(textView) 58 | 59 | textView.translatesAutoresizingMaskIntoConstraints = false 60 | textView.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8).isActive = true 61 | textView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true 62 | textView.rightAnchor.constraint(equalTo: webView.leftAnchor, constant: -8).isActive = true 63 | textView.heightAnchor.constraint(equalToConstant: 200).isActive = true 64 | textView.layer.borderWidth = 1.0 65 | textView.layer.cornerRadius = 6.0 66 | textView.layer.borderColor = UIColor(red: 229/255.0, green: 229/255.0, blue: 229/255, alpha: 1.0).cgColor 67 | textField.becomeFirstResponder() 68 | } 69 | 70 | func rightBarButtonItemDidTap(_ button: UIBarButtonItem) { 71 | let imageData: Data 72 | do { 73 | imageData = try Data(contentsOf: imageURL) 74 | } catch { 75 | showAlertContrller(title: "Error", message: "can not create Data") 76 | return 77 | } 78 | reportIssue(imageData: imageData) 79 | } 80 | 81 | func leftBarButtonItemDidTap(_ button: UIBarButtonItem) { 82 | dismiss(animated: true) { } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Source/Classes/PostViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostViewController.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/12. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PostViewControllerProtocol: class { 12 | var textField: UITextField { get } 13 | var textView: UITextView { get } 14 | } 15 | extension PostViewControllerProtocol where Self: UIViewController { 16 | func showAlertContrller(title: String, message: String) { 17 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 18 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { _ in 19 | self.dismiss(animated: true) { } 20 | })) 21 | 22 | if Thread.isMainThread { 23 | self.present(alert, animated: true) { } 24 | } else { 25 | DispatchQueue.main.sync { 26 | self.present(alert, animated: true) { } 27 | } 28 | } 29 | } 30 | 31 | func reportIssue(imageData: Data) { 32 | 33 | let window = Ajimi.window 34 | let reporter = Reporter(options: window.options) 35 | let title = textField.text ?? "" 36 | let body = textView.text ?? "" 37 | 38 | reporter.submit(title: title, body: body, screenshotData: imageData) { result in 39 | switch result { 40 | case .success: 41 | self.showAlertContrller(title: "Success", message: "Posted.") 42 | case .failure(let error): 43 | self.showAlertContrller(title: "Error", message: error.localizedDescription) 44 | } 45 | } 46 | } 47 | } 48 | 49 | class PostViewController: UIViewController, PostViewControllerProtocol { 50 | 51 | let image: UIImage 52 | let textField = UITextField(frame: .zero) 53 | let textView = UITextView(frame: .zero) 54 | 55 | init(image: UIImage) { 56 | self.image = image 57 | super.init(nibName: nil, bundle: nil) 58 | } 59 | 60 | required init?(coder aDecoder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | 64 | override func viewDidLoad() { 65 | super.viewDidLoad() 66 | 67 | view.backgroundColor = .white 68 | 69 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Send", style: UIBarButtonItemStyle.plain, target: self, action: #selector(rightBarButtonItemDidTap(_:))) 70 | 71 | let imageView = UIImageView(frame: .zero) 72 | imageView.image = image 73 | imageView.contentMode = .scaleAspectFit 74 | 75 | view.addSubview(imageView) 76 | 77 | imageView.translatesAutoresizingMaskIntoConstraints = false 78 | imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true 79 | imageView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 80 | imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true 81 | imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true 82 | imageView.backgroundColor = .gray 83 | view.addSubview(textField) 84 | 85 | textField.translatesAutoresizingMaskIntoConstraints = false 86 | textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 64 + 8).isActive = true 87 | textField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true 88 | textField.rightAnchor.constraint(equalTo: imageView.leftAnchor, constant: -8).isActive = true 89 | textField.heightAnchor.constraint(equalToConstant: 44).isActive = true 90 | textField.placeholder = "title" 91 | textField.borderStyle = UITextBorderStyle.roundedRect 92 | view.addSubview(textView) 93 | 94 | textView.translatesAutoresizingMaskIntoConstraints = false 95 | textView.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8).isActive = true 96 | textView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true 97 | textView.rightAnchor.constraint(equalTo: imageView.leftAnchor, constant: -8).isActive = true 98 | textView.heightAnchor.constraint(equalToConstant: 200).isActive = true 99 | textView.layer.borderWidth = 1.0 100 | textView.layer.cornerRadius = 6.0 101 | textView.layer.borderColor = UIColor(red: 229/255.0, green: 229/255.0, blue: 229/255, alpha: 1.0).cgColor 102 | textField.becomeFirstResponder() 103 | } 104 | 105 | func rightBarButtonItemDidTap(_ button: UIBarButtonItem) { 106 | guard let imageData = UIImagePNGRepresentation(image) else { 107 | showAlertContrller(title: "Error", message: "can not create UIImagePNGRepresentation") 108 | return 109 | } 110 | reportIssue(imageData: imageData) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Source/Classes/RecordButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordButton.swift 3 | // Ajimi 4 | // 5 | // Created by daichi-nakajima on 2017/07/23. 6 | // Copyright © 2017年 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RecordButton: UIButton { 12 | 13 | private var progressLayer: CAShapeLayer! 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | backgroundColor = UIColor.clear 18 | } 19 | 20 | public required init(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder)! 22 | backgroundColor = UIColor.clear 23 | } 24 | 25 | public override func draw(_ rect: CGRect) { 26 | createProgressLayer() 27 | } 28 | 29 | private func createProgressLayer() { 30 | let startAngle = -Double.pi / 2.0 31 | let endAngle = Double.pi + 1.5 32 | let centerPoint = CGPoint(x: frame.width / 2, y: frame.height / 2) 33 | 34 | progressLayer = CAShapeLayer() 35 | let bezierPath = UIBezierPath( 36 | arcCenter: centerPoint, 37 | radius: 25.0, 38 | startAngle: CGFloat(startAngle), 39 | endAngle: CGFloat(endAngle), clockwise: true 40 | ) 41 | progressLayer.path = bezierPath.cgPath 42 | progressLayer.backgroundColor = UIColor.clear.cgColor 43 | progressLayer.fillColor = UIColor.clear.cgColor 44 | progressLayer.strokeColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.6).cgColor 45 | progressLayer.lineWidth = 4.0 46 | progressLayer.strokeStart = 0.0 47 | progressLayer.strokeEnd = 0.0 48 | progressLayer.lineCap = kCALineCapRound 49 | layer.addSublayer(progressLayer) 50 | } 51 | 52 | func reset() { 53 | progressLayer.removeFromSuperlayer() 54 | setNeedsDisplay() 55 | } 56 | 57 | func animateCurveToProgress(progress: Float) { 58 | 59 | if progressLayer == nil { 60 | return 61 | } 62 | 63 | let animation = CABasicAnimation(keyPath: "strokeEnd") 64 | animation.fromValue = NSNumber(value: Float(progressLayer.strokeEnd)) 65 | animation.toValue = NSNumber(value: progress) 66 | animation.duration = 0.05 67 | animation.fillMode = kCAFillModeForwards 68 | progressLayer.strokeEnd = CGFloat(progress) 69 | progressLayer.add(animation, forKey: "strokeEnd") 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Source/Classes/RectangleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RectangleView.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/12. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RectangleView: UIView { 12 | 13 | let lengthOfTouchSide: CGFloat = 20.0 14 | let merginOfRectangle: CGFloat = 10 15 | var rectangleLayer = CAShapeLayer() 16 | 17 | enum TransformArea: Int { 18 | case leftTop 19 | case rightTop 20 | case rightBottom 21 | case leftBottom 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | 31 | let movePanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleMovePanGesture(_:))) 32 | addGestureRecognizer(movePanGestureRecognizer) 33 | 34 | backgroundColor = .clear 35 | 36 | self.addSubview(leftTopView) 37 | self.addSubview(rightTopView) 38 | 39 | self.addSubview(leftBottomView) 40 | self.addSubview(rightBottomView) 41 | } 42 | 43 | lazy var leftTopView: UIView = { 44 | let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: self.lengthOfTouchSide, height: self.lengthOfTouchSide)) 45 | view.backgroundColor = .clear 46 | view.tag = TransformArea.leftTop.rawValue 47 | 48 | let circleLayer = CAShapeLayer() 49 | let radius: CGFloat = 10.0 50 | circleLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: view.frame.size), cornerRadius: radius).cgPath 51 | circleLayer.position = CGPoint(x: 0, y: 0) 52 | circleLayer.fillColor = UIColor.blue.cgColor 53 | view.layer.addSublayer(circleLayer) 54 | 55 | let recongnizer = UIPanGestureRecognizer(target: self, action: #selector(handleTransformMovePanGesture(_:))) 56 | view.addGestureRecognizer(recongnizer) 57 | 58 | return view 59 | }() 60 | lazy var rightTopView: UIView = { 61 | let view = UIView(frame: CGRect(x: self.frame.width - self.lengthOfTouchSide, y: 0.0, width: self.lengthOfTouchSide, height: self.lengthOfTouchSide)) 62 | view.backgroundColor = .clear 63 | view.tag = TransformArea.rightTop.rawValue 64 | 65 | let circleLayer = CAShapeLayer() 66 | let radius: CGFloat = 10.0 67 | circleLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: view.frame.size), cornerRadius: radius).cgPath 68 | circleLayer.position = CGPoint(x: 0, y: 0) 69 | circleLayer.fillColor = UIColor.blue.cgColor 70 | view.layer.addSublayer(circleLayer) 71 | 72 | let recongnizer = UIPanGestureRecognizer(target: self, action: #selector(handleTransformMovePanGesture(_:))) 73 | view.addGestureRecognizer(recongnizer) 74 | 75 | return view 76 | }() 77 | 78 | lazy var rightBottomView: UIView = { 79 | let view = UIView(frame: CGRect(x: 0, y: self.frame.height - self.lengthOfTouchSide, width: self.lengthOfTouchSide, height: self.lengthOfTouchSide)) 80 | view.backgroundColor = .clear 81 | view.tag = TransformArea.rightBottom.rawValue 82 | 83 | let circleLayer = CAShapeLayer() 84 | let radius: CGFloat = 10.0 85 | circleLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: view.frame.size), cornerRadius: radius).cgPath 86 | circleLayer.position = CGPoint(x: 0, y: 0) 87 | circleLayer.fillColor = UIColor.blue.cgColor 88 | view.layer.addSublayer(circleLayer) 89 | 90 | let recongnizer = UIPanGestureRecognizer(target: self, action: #selector(handleTransformMovePanGesture(_:))) 91 | view.addGestureRecognizer(recongnizer) 92 | self.addSubview(view) 93 | 94 | return view 95 | }() 96 | 97 | lazy var leftBottomView: UIView = { 98 | let view = UIView(frame: CGRect(x: self.frame.width - self.lengthOfTouchSide, y: self.frame.height - self.lengthOfTouchSide, width: self.lengthOfTouchSide, height: self.lengthOfTouchSide)) 99 | view.backgroundColor = .clear 100 | view.tag = TransformArea.leftBottom.rawValue 101 | 102 | let circleLayer = CAShapeLayer() 103 | let radius: CGFloat = 10.0 104 | circleLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: view.frame.size), cornerRadius: radius).cgPath 105 | circleLayer.position = CGPoint(x: 0, y: 0) 106 | circleLayer.fillColor = UIColor.blue.cgColor 107 | view.layer.addSublayer(circleLayer) 108 | 109 | let recongnizer = UIPanGestureRecognizer(target: self, action: #selector(handleTransformMovePanGesture(_:))) 110 | view.addGestureRecognizer(recongnizer) 111 | 112 | return view 113 | }() 114 | 115 | override func draw(_ rect: CGRect) { 116 | super.draw(rect) 117 | 118 | let rect = rectangleRect(frame) 119 | rectangleLayer.path = UIBezierPath(rect: rect).cgPath 120 | rectangleLayer.lineWidth = 2.0 121 | rectangleLayer.strokeColor = UIColor.red.cgColor 122 | rectangleLayer.fillColor = UIColor.clear.cgColor 123 | rectangleLayer.backgroundColor = UIColor.clear.cgColor 124 | layer.addSublayer(rectangleLayer) 125 | } 126 | 127 | func createShapeLayer() -> CAShapeLayer { 128 | let rect = rectangleRect(frame) 129 | let layer = CAShapeLayer() 130 | layer.path = UIBezierPath(rect: rect).cgPath 131 | layer.lineWidth = 2.0 132 | layer.strokeColor = UIColor.red.cgColor 133 | layer.fillColor = UIColor.clear.cgColor 134 | layer.backgroundColor = UIColor.clear.cgColor 135 | return layer 136 | } 137 | 138 | @objc private func handleMovePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { 139 | switch gestureRecognizer.state { 140 | case .changed: 141 | let point = gestureRecognizer.translation(in: self) 142 | let center = CGPoint(x: gestureRecognizer.view!.center.x + point.x, y: gestureRecognizer.view!.center.y + point.y) 143 | gestureRecognizer.view!.center = center 144 | gestureRecognizer.setTranslation(.zero, in: self) 145 | default: 146 | break 147 | } 148 | } 149 | 150 | var startTransform:CGAffineTransform! 151 | 152 | func rectangleRect() -> CGRect { 153 | var rect = rectangleRect(frame) 154 | rect.origin.x += frame.origin.x 155 | rect.origin.y += frame.origin.y 156 | return rect 157 | } 158 | 159 | private func rectangleRect(_ frame: CGRect) -> CGRect { 160 | return CGRect( 161 | x: merginOfRectangle, 162 | y: merginOfRectangle, 163 | width: frame.width - merginOfRectangle * 2, 164 | height: frame.height - merginOfRectangle * 2 165 | ) 166 | } 167 | 168 | private func updateTransformView() { 169 | rightTopView.frame = CGRect( 170 | x: frame.width - self.lengthOfTouchSide, 171 | y: 0.0, width: self.lengthOfTouchSide, 172 | height: self.lengthOfTouchSide 173 | ) 174 | leftBottomView.frame = CGRect( 175 | x: frame.width - lengthOfTouchSide, 176 | y: frame.height - lengthOfTouchSide, 177 | width: lengthOfTouchSide, 178 | height: lengthOfTouchSide 179 | ) 180 | rightBottomView.frame = CGRect( 181 | x: 0.0, 182 | y: frame.height - lengthOfTouchSide, 183 | width: lengthOfTouchSide, 184 | height: lengthOfTouchSide 185 | ) 186 | } 187 | 188 | @objc private func handleTransformMovePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { 189 | switch gestureRecognizer.state { 190 | case .changed: 191 | let point = gestureRecognizer.translation(in: self) 192 | 193 | guard let targetView = gestureRecognizer.view else { return } 194 | guard let transformArea = TransformArea(rawValue: targetView.tag) else { return } 195 | 196 | switch transformArea { 197 | case .leftTop: 198 | var originalFrame = frame 199 | originalFrame.origin.x += point.x 200 | originalFrame.origin.y += point.y 201 | originalFrame.size = CGSize(width: frame.width - point.x, height: frame.height - point.y) 202 | frame = originalFrame 203 | case .rightTop: 204 | var originalFrame = frame 205 | originalFrame.origin.y += point.y 206 | originalFrame.size = CGSize(width: frame.width + point.x, height: frame.height - point.y) 207 | frame = originalFrame 208 | case .rightBottom: 209 | var originalFrame = frame 210 | originalFrame.origin.x += point.x 211 | originalFrame.size = CGSize(width: frame.width - point.x, height: frame.height + point.y) 212 | frame = originalFrame 213 | case .leftBottom: 214 | var originalFrame = frame 215 | originalFrame.size = CGSize(width: frame.width + point.x, height: frame.height + point.y) 216 | frame = originalFrame 217 | } 218 | 219 | let rect = rectangleRect(frame) 220 | rectangleLayer.path = UIBezierPath(rect: rect).cgPath 221 | updateTransformView() 222 | 223 | gestureRecognizer.setTranslation(.zero, in: self) 224 | default: 225 | break 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Source/Classes/Reporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reporter.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/15. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Reporter { 12 | 13 | private let options: AjimiOptions 14 | public init(options: AjimiOptions) { 15 | self.options = options 16 | } 17 | 18 | internal func submit(title: String, body: String, screenshotData: Data, completion: @escaping (Result) -> Void) { 19 | ImageUploder.uploadImage(url: options.imageUploadURL, key: options.imageUploadKey, imageData: screenshotData) { result in 20 | switch result { 21 | case .success(let imageUrlString): 22 | var issueBody = "\(body) \n\n ![image](\(imageUrlString)) \n\n ----\n" 23 | Device.all.forEach { key, value in issueBody += "\(key): \(value)\n" } 24 | self.createIssue(issueTitle: title, issueBody: issueBody, screenshotURL: nil, completion: completion) 25 | case .failure(let error): 26 | completion(Result.failure(error)) 27 | } 28 | } 29 | } 30 | 31 | private func createIssue(issueTitle: String, issueBody: String, screenshotURL: String?, completion: @escaping (Result) -> Void) { 32 | let payload: [String:Any] = ["title": "Ajimi: " + issueTitle, "body": issueBody] 33 | var jsonSerializationData: Data? 34 | 35 | do { 36 | jsonSerializationData = try JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted) 37 | } catch let error as NSError { 38 | completion(Result.failure(error)) 39 | } 40 | 41 | guard let jsonData = jsonSerializationData else { return } 42 | guard var request = createRequest() else { return } 43 | 44 | request.httpBody = jsonData 45 | let task = URLSession.shared.dataTask(with: request) { (_, response, error) in 46 | guard let response = response as? HTTPURLResponse else { 47 | return 48 | } 49 | 50 | if error != nil { 51 | DispatchQueue.main.sync { 52 | completion(Result.failure(ReporterError.NetworkError(error.debugDescription))) 53 | } 54 | return 55 | } 56 | 57 | if response.statusCode != 201 { 58 | DispatchQueue.main.sync { 59 | completion(Result.failure(ReporterError.GitHubSaveError("repo \(self.options.github.repo)."))) 60 | } 61 | return 62 | } 63 | 64 | completion(Result.success(true)) 65 | } 66 | task.resume() 67 | 68 | } 69 | 70 | // https://developer.github.com/v3/issues/#create-an-issue 71 | private func createRequest() -> URLRequest? { 72 | let basePath = options.github.basePath 73 | let repo = options.github.repo 74 | let user = options.github.user 75 | let token = options.github.accessToken 76 | 77 | let url = URL(string: "\(basePath)/repos/\(user)/\(repo)/issues")! 78 | var request = URLRequest(url: url) 79 | request.httpMethod = "POST" 80 | 81 | let planeText = "\(user):\(token)" 82 | let basicAuth = "Basic \(planeText.base64)" 83 | 84 | request.setValue(basicAuth, forHTTPHeaderField: "Authorization") 85 | return request 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Source/Classes/ScreenRecorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenRecorder.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/17. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import ImageIO 12 | import MobileCoreServices 13 | 14 | protocol ScreenRecorderDelegate: class { 15 | func screenRecordDidRefresh(_ recorder: ScreenRecorder, progress: Double) 16 | } 17 | 18 | class ScreenRecorder { 19 | var isRecording = false 20 | var videoURL: URL? { 21 | didSet { 22 | debugPrint(videoURL ?? "") 23 | } 24 | } 25 | private var displayLink: CADisplayLink! 26 | private var keyWindow = Ajimi.targetWindow 27 | var recordTime: CFTimeInterval = 5.0 28 | var images: [UIImage] = [] 29 | var startTime: CFTimeInterval = 0.0 30 | weak var delegate: ScreenRecorderDelegate? 31 | 32 | public func startRecording() { 33 | if !isRecording { 34 | isRecording = true 35 | startTime = CACurrentMediaTime() 36 | displayLink = CADisplayLink(target: self, selector: #selector(displayDidRefresh(_:))) 37 | displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) 38 | } 39 | } 40 | public func stopRecording(completion: @escaping () -> Void) { 41 | if isRecording { 42 | isRecording = false 43 | displayLink.remove(from: RunLoop.main, forMode: RunLoopMode.commonModes) 44 | completeRecordingSession(completion: completion) 45 | } 46 | } 47 | 48 | @objc func displayDidRefresh(_ sender: CADisplayLink) { 49 | UIGraphicsBeginImageContextWithOptions(keyWindow.bounds.size, true, 0) 50 | keyWindow.drawHierarchy(in: keyWindow.bounds, afterScreenUpdates: false) 51 | let image = UIGraphicsGetImageFromCurrentImageContext() 52 | UIGraphicsEndImageContext() 53 | 54 | let resizedImage = image!.resize(size: CGSize(width: image!.size.width / 2.0, height: image!.size.height / 2.0))! 55 | 56 | images.append(resizedImage) 57 | 58 | let current = (CACurrentMediaTime() - startTime) / recordTime 59 | delegate?.screenRecordDidRefresh(self, progress: current) 60 | } 61 | 62 | func clean() { 63 | images = [] 64 | } 65 | 66 | func completeRecordingSession(completion: @escaping () -> Void) { 67 | createGIF() 68 | DispatchQueue.main.async { 69 | completion() 70 | self.clean() 71 | } 72 | } 73 | 74 | func createGIF() { 75 | let loopCount = 0 76 | let frameCount = images.count 77 | let delayTime = recordTime / Double(images.count) 78 | let destinationFileURL = videoURL! 79 | 80 | let fileProperties = [ 81 | kCGImagePropertyGIFDictionary as String: [ 82 | kCGImagePropertyGIFLoopCount as String: NSNumber(value: Int32(loopCount) as Int32) 83 | ], 84 | kCGImagePropertyGIFHasGlobalColorMap as String: NSValue(nonretainedObject: true) 85 | ] as [String : Any] 86 | 87 | let frameProperties = [ 88 | kCGImagePropertyGIFDictionary as String:[ 89 | kCGImagePropertyGIFDelayTime as String:delayTime 90 | ] 91 | ] 92 | 93 | guard let destination = CGImageDestinationCreateWithURL(destinationFileURL as CFURL, kUTTypeGIF, frameCount, nil) else { 94 | assertionFailure("dest not found = \(destinationFileURL.absoluteString)") 95 | return 96 | } 97 | CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) 98 | 99 | images.forEach { image in 100 | CGImageDestinationAddImage(destination, image.cgImage!, frameProperties as CFDictionary) 101 | } 102 | 103 | if !CGImageDestinationFinalize(destination) { 104 | assertionFailure("finalize error") 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /Source/Classes/String+Ajimi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/16. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | var base64: String { 13 | let userPasswordData = data(using: String.Encoding.utf8) 14 | let base64EncodedCredential = userPasswordData?.base64EncodedString() 15 | return base64EncodedCredential! 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/Classes/TopViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopViewController.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/11. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TopViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | view.isUserInteractionEnabled = false 17 | view.backgroundColor = .clear 18 | } 19 | 20 | override func viewWillAppear(_ animated: Bool) { 21 | super.viewWillAppear(animated) 22 | } 23 | 24 | func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { 25 | 26 | guard let gesturingView = gestureRecognizer.view else { 27 | return 28 | } 29 | 30 | switch gestureRecognizer.state { 31 | case .began: 32 | gesturingView.alpha = 1.0 33 | case .changed: 34 | let point = gestureRecognizer.translation(in: view) 35 | let center = CGPoint( 36 | x: gesturingView.center.x + point.x, 37 | y: gesturingView.center.y + point.y 38 | ) 39 | gesturingView.center = center 40 | gestureRecognizer.setTranslation(.zero, in: view) 41 | default: 42 | let point = gesturingView.center 43 | let width = gesturingView.bounds.width 44 | let height = gesturingView.bounds.height 45 | 46 | var x = point.x 47 | var y = point.y 48 | 49 | if y - 60.0 <= view.bounds.minY { 50 | if x - width * 0.5 <= view.bounds.minX { 51 | x = view.bounds.minX + width * 0.5 52 | } else if view.bounds.maxX <= x + width * 0.5 { 53 | x = view.bounds.maxX - width * 0.5 54 | } 55 | y = view.bounds.minY + height * 0.5 56 | } else if view.bounds.maxY <= y + 60.0 { 57 | if x - width * 0.5 <= view.bounds.minX { 58 | x = view.bounds.minX + width * 0.5 59 | } else if view.bounds.maxX <= x + width * 0.5 { 60 | x = view.bounds.maxX - width * 0.5 61 | } 62 | y = view.bounds.maxY - height * 0.5 63 | } else { 64 | if x < view.bounds.width * 0.5 { 65 | x = view.bounds.minX + width * 0.5 66 | } else { 67 | x = view.bounds.maxX - width * 0.5 68 | } 69 | } 70 | 71 | let center = CGPoint(x: x, y: y) 72 | 73 | UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: { 74 | gesturingView.center = center 75 | }, completion: { _ in 76 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { 77 | guard center.equalTo(gesturingView.center) else { 78 | return 79 | } 80 | 81 | UIView.animate(withDuration: 0.3) { 82 | gestureRecognizer.view!.alpha = 0.3 83 | } 84 | } 85 | }) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Source/Classes/UIImage+Ajimi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Ajimi.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/23. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func resize(size specifiedSize: CGSize) -> UIImage? { 13 | let widthRatio = specifiedSize.width / size.width 14 | let heightRatio = specifiedSize.height / size.height 15 | let ratio = widthRatio < heightRatio ? widthRatio : heightRatio 16 | 17 | let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio) 18 | 19 | UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) 20 | draw(in: CGRect(origin: .zero, size: resizedSize)) 21 | let resizedImage = UIGraphicsGetImageFromCurrentImageContext()! 22 | UIGraphicsEndImageContext() 23 | 24 | return resizedImage 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/Classes/Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Window.swift 3 | // Ajimi 4 | // 5 | // Created by nakajijapan on 2017/07/11. 6 | // Copyright © 2017 nakajijapan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class Window: UIWindow { 12 | 13 | var options: AjimiOptions! 14 | fileprivate let snapshotButton = UIButton(type: .system) 15 | fileprivate let recordButton = RecordButton(type: .system) 16 | fileprivate let stackView = UIStackView(frame: .zero) 17 | 18 | override var rootViewController: UIViewController? { 19 | didSet { 20 | if let TopViewController = rootViewController as? TopViewController { 21 | 22 | let cameraPath = resourceBundle().path(forResource: "camera", ofType: "png")! 23 | let cameraImage = UIImage(contentsOfFile: cameraPath) 24 | snapshotButton.setImage(cameraImage, for: .normal) 25 | snapshotButton.addTarget(self, 26 | action: #selector(snapshotButtonDidTap(_:)), 27 | for: .touchUpInside) 28 | 29 | let videoPath = resourceBundle().path(forResource: "video", ofType: "png")! 30 | let videoImage = UIImage(contentsOfFile: videoPath) 31 | 32 | recordButton.setImage(videoImage, for: .normal) 33 | recordButton.setTitleColor(.black, for: .normal) 34 | recordButton.addTarget(self, 35 | action: #selector(recordButtonDidTap(_:)), 36 | for: .touchUpInside) 37 | 38 | stackView.frame = CGRect( 39 | x: 0.0, 40 | y: self.bounds.height * 0.3, 41 | width: cameraImage!.size.width + 8, 42 | height: videoImage!.size.height * 2.0 43 | ) 44 | 45 | stackView.alignment = UIStackViewAlignment.center 46 | stackView.axis = UILayoutConstraintAxis.vertical 47 | stackView.addArrangedSubview(snapshotButton) 48 | stackView.addArrangedSubview(recordButton) 49 | self.addSubview(stackView) 50 | let panGestureRecognizer = UIPanGestureRecognizer( 51 | target: rootViewController, 52 | action: #selector(TopViewController.handlePanGesture(_:))) 53 | stackView.addGestureRecognizer(panGestureRecognizer) 54 | } 55 | } 56 | } 57 | 58 | private func resourceBundle() -> Bundle { 59 | let bundlePath = Bundle.main.path( 60 | forResource: "Ajimi", 61 | ofType: "bundle", 62 | inDirectory: "Frameworks/Ajimi.framework" 63 | ) 64 | 65 | if bundlePath != nil { 66 | return Bundle(path: bundlePath!)! 67 | } 68 | 69 | return Bundle(for: type(of: self)) 70 | } 71 | 72 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 73 | let view = super.hitTest(point, with: event) 74 | if view === self { 75 | return nil 76 | } 77 | 78 | return view 79 | } 80 | 81 | } 82 | 83 | // MARK: - Button Actions 84 | extension Window { 85 | 86 | func snapshotButtonDidTap(_ button: UIButton) { 87 | button.alpha = 1.0 88 | 89 | guard let image = takePhoto() else { 90 | return 91 | } 92 | 93 | let viewController = ImageEditViewController(image: image) 94 | let navigationController = UINavigationController(rootViewController: viewController) 95 | rootViewController?.present(navigationController, animated: true) { } 96 | } 97 | 98 | private func takePhoto() -> UIImage? { 99 | let window = Ajimi.targetWindow 100 | UIGraphicsBeginImageContext(window.bounds.size) 101 | window.layer.render(in: UIGraphicsGetCurrentContext()!) 102 | let image = UIGraphicsGetImageFromCurrentImageContext() 103 | UIGraphicsEndImageContext() 104 | 105 | return image 106 | } 107 | 108 | func recordButtonDidTap(_ button: UIButton) { 109 | 110 | let outputPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!.appending("/ajimi.gif") 111 | if FileManager.default.fileExists(atPath: outputPath) { 112 | do { 113 | try FileManager.default.removeItem(atPath: outputPath) 114 | } catch { 115 | fatalError("failed to delete the file \(outputPath)") 116 | } 117 | } 118 | 119 | let screenRecorder = ScreenRecorder() 120 | screenRecorder.videoURL = URL(fileURLWithPath: outputPath) 121 | screenRecorder.startRecording() 122 | screenRecorder.delegate = self 123 | 124 | DispatchQueue.main.asyncAfter(deadline: .now() + screenRecorder.recordTime) { 125 | screenRecorder.stopRecording { 126 | let viewController = PostGIFViewController(imageURL: screenRecorder.videoURL!) 127 | let navigationController = UINavigationController(rootViewController: viewController) 128 | self.rootViewController?.present(navigationController, animated: true) { } 129 | } 130 | } 131 | } 132 | 133 | } 134 | 135 | // MARK: - ScreenRecorderDelegate 136 | extension Window: ScreenRecorderDelegate { 137 | 138 | func screenRecordDidRefresh(_ recorder: ScreenRecorder, progress: Double) { 139 | recordButton.animateCurveToProgress(progress: Float(progress)) 140 | if progress >= 1.0 { 141 | recordButton.reset() 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /demo_snapshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/demo_snapshot.gif -------------------------------------------------------------------------------- /demo_video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakajijapan/Ajimi/f9928e84e46dd2a600584e26cd319175321a1958/demo_video.gif --------------------------------------------------------------------------------