├── .gitignore ├── LICENSE ├── PeekView.podspec ├── PeekViewDemo ├── PeekViewDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── PeekViewDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── appletv.imageset │ │ │ ├── Contents.json │ │ │ └── appletv_medium_2x.jpg │ │ ├── ipad.imageset │ │ │ ├── Contents.json │ │ │ └── ipad_pro_medium_2x.jpg │ │ ├── iphone.imageset │ │ │ ├── Contents.json │ │ │ └── iphone-6s-change_medium_2x.jpg │ │ ├── macbook.imageset │ │ │ ├── Contents.json │ │ │ └── macbook_medium_2x.jpg │ │ └── watch.imageset │ │ │ ├── Contents.json │ │ │ └── watch_medium_2x.jpg │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── DemoViewController.swift │ ├── DetailViewController.swift │ └── Info.plist └── PeekViewDemoTests │ ├── Info.plist │ └── PeekViewDemoTests.swift ├── README.md ├── Source ├── Assets │ ├── arrow@2x.png │ ├── arrow@3x.png │ ├── checked@2x.png │ └── checked@3x.png └── PeekView.swift ├── peekview.gif └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Huong Do 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PeekView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "PeekView" 4 | s.version = "1.2.0" 5 | s.summary = "PeekView supports peek, pop and preview actions for iOS devices without 3D Touch capibility" 6 | 7 | s.description = "When implementing peek, pop and preview actions with 3D Touch, you may want to support such features for users accessing your app from older devices that don't provide 3D Touch capibility. PeekView hence can be used as an alternative in such case." 8 | 9 | s.homepage = "https://github.com/itsmeichigo/PeekView" 10 | s.screenshots = "https://github.com/itsmeichigo/PeekView/raw/master/peekview.gif", "https://github.com/itsmeichigo/PeekView/raw/master/screenshot.png" 11 | 12 | s.license = { :type => "MIT", :file => "LICENSE" } 13 | 14 | s.author = { "Huong Do" => "huongdt29@gmail.com" } 15 | s.social_media_url = "http://twitter.com/itsmeichigo" 16 | 17 | s.ios.deployment_target = "8.0" 18 | 19 | s.source = { :git => "https://github.com/itsmeichigo/PeekView.git", :tag => s.version } 20 | 21 | s.source_files = "Source" 22 | 23 | s.resources = "Source/Assets/*.png" 24 | #s.resource_bundles = { "PeekView" => ["Pod/Assets/*.png"] } 25 | 26 | s.requires_arc = true 27 | s.swift_versions = ['4.0', '4.2', '5'] 28 | 29 | end 30 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2C5553141C6C529700D9C815 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5553131C6C529700D9C815 /* AppDelegate.swift */; }; 11 | 2C5553161C6C529700D9C815 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5553151C6C529700D9C815 /* DemoViewController.swift */; }; 12 | 2C5553191C6C529700D9C815 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C5553171C6C529700D9C815 /* Main.storyboard */; }; 13 | 2C55531B1C6C529700D9C815 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C55531A1C6C529700D9C815 /* Assets.xcassets */; }; 14 | 2C55531E1C6C529700D9C815 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C55531C1C6C529700D9C815 /* LaunchScreen.storyboard */; }; 15 | 2C5553291C6C529700D9C815 /* PeekViewDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5553281C6C529700D9C815 /* PeekViewDemoTests.swift */; }; 16 | 2C5553351C6C52C300D9C815 /* PeekView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5553341C6C52C300D9C815 /* PeekView.swift */; }; 17 | 2C5553371C6C532F00D9C815 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5553361C6C532F00D9C815 /* DetailViewController.swift */; }; 18 | 2CB374321C6DD53200A9F255 /* arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2CB3742E1C6DD53200A9F255 /* arrow@2x.png */; }; 19 | 2CB374331C6DD53200A9F255 /* arrow@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2CB3742F1C6DD53200A9F255 /* arrow@3x.png */; }; 20 | 2CB374341C6DD53200A9F255 /* checked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2CB374301C6DD53200A9F255 /* checked@2x.png */; }; 21 | 2CB374351C6DD53200A9F255 /* checked@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2CB374311C6DD53200A9F255 /* checked@3x.png */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 2C5553251C6C529700D9C815 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 2C5553081C6C529700D9C815 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 2C55530F1C6C529700D9C815; 30 | remoteInfo = PeekViewDemo; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 2C5553101C6C529700D9C815 /* PeekViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PeekViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 2C5553131C6C529700D9C815 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 2C5553151C6C529700D9C815 /* DemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = ""; }; 38 | 2C5553181C6C529700D9C815 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 2C55531A1C6C529700D9C815 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 2C55531D1C6C529700D9C815 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 2C55531F1C6C529700D9C815 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 2C5553241C6C529700D9C815 /* PeekViewDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PeekViewDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 2C5553281C6C529700D9C815 /* PeekViewDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekViewDemoTests.swift; sourceTree = ""; }; 44 | 2C55532A1C6C529700D9C815 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 2C5553341C6C52C300D9C815 /* PeekView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeekView.swift; sourceTree = ""; }; 46 | 2C5553361C6C532F00D9C815 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 47 | 2CB3742E1C6DD53200A9F255 /* arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow@2x.png"; sourceTree = ""; }; 48 | 2CB3742F1C6DD53200A9F255 /* arrow@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow@3x.png"; sourceTree = ""; }; 49 | 2CB374301C6DD53200A9F255 /* checked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checked@2x.png"; sourceTree = ""; }; 50 | 2CB374311C6DD53200A9F255 /* checked@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checked@3x.png"; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 2C55530D1C6C529700D9C815 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | 2C5553211C6C529700D9C815 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 2C5553071C6C529700D9C815 = { 72 | isa = PBXGroup; 73 | children = ( 74 | 2C5553121C6C529700D9C815 /* PeekViewDemo */, 75 | 2C5553271C6C529700D9C815 /* PeekViewDemoTests */, 76 | 2C5553111C6C529700D9C815 /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | 2C5553111C6C529700D9C815 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 2C5553101C6C529700D9C815 /* PeekViewDemo.app */, 84 | 2C5553241C6C529700D9C815 /* PeekViewDemoTests.xctest */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 2C5553121C6C529700D9C815 /* PeekViewDemo */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 2C5553331C6C52B500D9C815 /* Source */, 93 | 2C5553131C6C529700D9C815 /* AppDelegate.swift */, 94 | 2C5553151C6C529700D9C815 /* DemoViewController.swift */, 95 | 2C5553361C6C532F00D9C815 /* DetailViewController.swift */, 96 | 2C5553171C6C529700D9C815 /* Main.storyboard */, 97 | 2C55531A1C6C529700D9C815 /* Assets.xcassets */, 98 | 2C55531C1C6C529700D9C815 /* LaunchScreen.storyboard */, 99 | 2C55531F1C6C529700D9C815 /* Info.plist */, 100 | ); 101 | path = PeekViewDemo; 102 | sourceTree = ""; 103 | }; 104 | 2C5553271C6C529700D9C815 /* PeekViewDemoTests */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 2C5553281C6C529700D9C815 /* PeekViewDemoTests.swift */, 108 | 2C55532A1C6C529700D9C815 /* Info.plist */, 109 | ); 110 | path = PeekViewDemoTests; 111 | sourceTree = ""; 112 | }; 113 | 2C5553331C6C52B500D9C815 /* Source */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 2CB3742D1C6DD53200A9F255 /* Assets */, 117 | 2C5553341C6C52C300D9C815 /* PeekView.swift */, 118 | ); 119 | name = Source; 120 | path = ../../Source; 121 | sourceTree = ""; 122 | }; 123 | 2CB3742D1C6DD53200A9F255 /* Assets */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 2CB3742E1C6DD53200A9F255 /* arrow@2x.png */, 127 | 2CB3742F1C6DD53200A9F255 /* arrow@3x.png */, 128 | 2CB374301C6DD53200A9F255 /* checked@2x.png */, 129 | 2CB374311C6DD53200A9F255 /* checked@3x.png */, 130 | ); 131 | path = Assets; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | 2C55530F1C6C529700D9C815 /* PeekViewDemo */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = 2C55532D1C6C529700D9C815 /* Build configuration list for PBXNativeTarget "PeekViewDemo" */; 140 | buildPhases = ( 141 | 2C55530C1C6C529700D9C815 /* Sources */, 142 | 2C55530D1C6C529700D9C815 /* Frameworks */, 143 | 2C55530E1C6C529700D9C815 /* Resources */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = PeekViewDemo; 150 | productName = PeekViewDemo; 151 | productReference = 2C5553101C6C529700D9C815 /* PeekViewDemo.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | 2C5553231C6C529700D9C815 /* PeekViewDemoTests */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = 2C5553301C6C529700D9C815 /* Build configuration list for PBXNativeTarget "PeekViewDemoTests" */; 157 | buildPhases = ( 158 | 2C5553201C6C529700D9C815 /* Sources */, 159 | 2C5553211C6C529700D9C815 /* Frameworks */, 160 | 2C5553221C6C529700D9C815 /* Resources */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | 2C5553261C6C529700D9C815 /* PBXTargetDependency */, 166 | ); 167 | name = PeekViewDemoTests; 168 | productName = PeekViewDemoTests; 169 | productReference = 2C5553241C6C529700D9C815 /* PeekViewDemoTests.xctest */; 170 | productType = "com.apple.product-type.bundle.unit-test"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 2C5553081C6C529700D9C815 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | LastSwiftUpdateCheck = 0720; 179 | LastUpgradeCheck = 1020; 180 | ORGANIZATIONNAME = "Huong Do"; 181 | TargetAttributes = { 182 | 2C55530F1C6C529700D9C815 = { 183 | CreatedOnToolsVersion = 7.2.1; 184 | LastSwiftMigration = 1020; 185 | }; 186 | 2C5553231C6C529700D9C815 = { 187 | CreatedOnToolsVersion = 7.2.1; 188 | LastSwiftMigration = 1020; 189 | TestTargetID = 2C55530F1C6C529700D9C815; 190 | }; 191 | }; 192 | }; 193 | buildConfigurationList = 2C55530B1C6C529700D9C815 /* Build configuration list for PBXProject "PeekViewDemo" */; 194 | compatibilityVersion = "Xcode 3.2"; 195 | developmentRegion = en; 196 | hasScannedForEncodings = 0; 197 | knownRegions = ( 198 | en, 199 | Base, 200 | ); 201 | mainGroup = 2C5553071C6C529700D9C815; 202 | productRefGroup = 2C5553111C6C529700D9C815 /* Products */; 203 | projectDirPath = ""; 204 | projectRoot = ""; 205 | targets = ( 206 | 2C55530F1C6C529700D9C815 /* PeekViewDemo */, 207 | 2C5553231C6C529700D9C815 /* PeekViewDemoTests */, 208 | ); 209 | }; 210 | /* End PBXProject section */ 211 | 212 | /* Begin PBXResourcesBuildPhase section */ 213 | 2C55530E1C6C529700D9C815 /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 2CB374321C6DD53200A9F255 /* arrow@2x.png in Resources */, 218 | 2CB374331C6DD53200A9F255 /* arrow@3x.png in Resources */, 219 | 2CB374341C6DD53200A9F255 /* checked@2x.png in Resources */, 220 | 2C55531E1C6C529700D9C815 /* LaunchScreen.storyboard in Resources */, 221 | 2C55531B1C6C529700D9C815 /* Assets.xcassets in Resources */, 222 | 2C5553191C6C529700D9C815 /* Main.storyboard in Resources */, 223 | 2CB374351C6DD53200A9F255 /* checked@3x.png in Resources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | 2C5553221C6C529700D9C815 /* Resources */ = { 228 | isa = PBXResourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXResourcesBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | 2C55530C1C6C529700D9C815 /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 2C5553161C6C529700D9C815 /* DemoViewController.swift in Sources */, 242 | 2C5553141C6C529700D9C815 /* AppDelegate.swift in Sources */, 243 | 2C5553351C6C52C300D9C815 /* PeekView.swift in Sources */, 244 | 2C5553371C6C532F00D9C815 /* DetailViewController.swift in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | 2C5553201C6C529700D9C815 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | 2C5553291C6C529700D9C815 /* PeekViewDemoTests.swift in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXSourcesBuildPhase section */ 257 | 258 | /* Begin PBXTargetDependency section */ 259 | 2C5553261C6C529700D9C815 /* PBXTargetDependency */ = { 260 | isa = PBXTargetDependency; 261 | target = 2C55530F1C6C529700D9C815 /* PeekViewDemo */; 262 | targetProxy = 2C5553251C6C529700D9C815 /* PBXContainerItemProxy */; 263 | }; 264 | /* End PBXTargetDependency section */ 265 | 266 | /* Begin PBXVariantGroup section */ 267 | 2C5553171C6C529700D9C815 /* Main.storyboard */ = { 268 | isa = PBXVariantGroup; 269 | children = ( 270 | 2C5553181C6C529700D9C815 /* Base */, 271 | ); 272 | name = Main.storyboard; 273 | sourceTree = ""; 274 | }; 275 | 2C55531C1C6C529700D9C815 /* LaunchScreen.storyboard */ = { 276 | isa = PBXVariantGroup; 277 | children = ( 278 | 2C55531D1C6C529700D9C815 /* Base */, 279 | ); 280 | name = LaunchScreen.storyboard; 281 | sourceTree = ""; 282 | }; 283 | /* End PBXVariantGroup section */ 284 | 285 | /* Begin XCBuildConfiguration section */ 286 | 2C55532B1C6C529700D9C815 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 292 | CLANG_CXX_LIBRARY = "libc++"; 293 | CLANG_ENABLE_MODULES = YES; 294 | CLANG_ENABLE_OBJC_ARC = YES; 295 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_COMMA = YES; 298 | CLANG_WARN_CONSTANT_CONVERSION = YES; 299 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 300 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 301 | CLANG_WARN_EMPTY_BODY = YES; 302 | CLANG_WARN_ENUM_CONVERSION = YES; 303 | CLANG_WARN_INFINITE_RECURSION = YES; 304 | CLANG_WARN_INT_CONVERSION = YES; 305 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 306 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 307 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 309 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 310 | CLANG_WARN_STRICT_PROTOTYPES = YES; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNREACHABLE_CODE = YES; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = dwarf; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | ENABLE_TESTABILITY = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_DYNAMIC_NO_PIC = NO; 321 | GCC_NO_COMMON_BLOCKS = YES; 322 | GCC_OPTIMIZATION_LEVEL = 0; 323 | GCC_PREPROCESSOR_DEFINITIONS = ( 324 | "DEBUG=1", 325 | "$(inherited)", 326 | ); 327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 329 | GCC_WARN_UNDECLARED_SELECTOR = YES; 330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 331 | GCC_WARN_UNUSED_FUNCTION = YES; 332 | GCC_WARN_UNUSED_VARIABLE = YES; 333 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 334 | MTL_ENABLE_DEBUG_INFO = YES; 335 | ONLY_ACTIVE_ARCH = YES; 336 | SDKROOT = iphoneos; 337 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | }; 340 | name = Debug; 341 | }; 342 | 2C55532C1C6C529700D9C815 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 348 | CLANG_CXX_LIBRARY = "libc++"; 349 | CLANG_ENABLE_MODULES = YES; 350 | CLANG_ENABLE_OBJC_ARC = YES; 351 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 352 | CLANG_WARN_BOOL_CONVERSION = YES; 353 | CLANG_WARN_COMMA = YES; 354 | CLANG_WARN_CONSTANT_CONVERSION = YES; 355 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 356 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 357 | CLANG_WARN_EMPTY_BODY = YES; 358 | CLANG_WARN_ENUM_CONVERSION = YES; 359 | CLANG_WARN_INFINITE_RECURSION = YES; 360 | CLANG_WARN_INT_CONVERSION = YES; 361 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 363 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 366 | CLANG_WARN_STRICT_PROTOTYPES = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 371 | COPY_PHASE_STRIP = NO; 372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 373 | ENABLE_NS_ASSERTIONS = NO; 374 | ENABLE_STRICT_OBJC_MSGSEND = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 384 | MTL_ENABLE_DEBUG_INFO = NO; 385 | SDKROOT = iphoneos; 386 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 387 | TARGETED_DEVICE_FAMILY = "1,2"; 388 | VALIDATE_PRODUCT = YES; 389 | }; 390 | name = Release; 391 | }; 392 | 2C55532E1C6C529700D9C815 /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 396 | INFOPLIST_FILE = PeekViewDemo/Info.plist; 397 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 399 | PRODUCT_BUNDLE_IDENTIFIER = com.ichigo.PeekViewDemo; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | SWIFT_VERSION = 5.0; 402 | }; 403 | name = Debug; 404 | }; 405 | 2C55532F1C6C529700D9C815 /* Release */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 409 | INFOPLIST_FILE = PeekViewDemo/Info.plist; 410 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 411 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 412 | PRODUCT_BUNDLE_IDENTIFIER = com.ichigo.PeekViewDemo; 413 | PRODUCT_NAME = "$(TARGET_NAME)"; 414 | SWIFT_VERSION = 5.0; 415 | }; 416 | name = Release; 417 | }; 418 | 2C5553311C6C529700D9C815 /* Debug */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | BUNDLE_LOADER = "$(TEST_HOST)"; 422 | INFOPLIST_FILE = PeekViewDemoTests/Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 424 | PRODUCT_BUNDLE_IDENTIFIER = com.ichigo.PeekViewDemoTests; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | SWIFT_VERSION = 5.0; 427 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PeekViewDemo.app/PeekViewDemo"; 428 | }; 429 | name = Debug; 430 | }; 431 | 2C5553321C6C529700D9C815 /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | BUNDLE_LOADER = "$(TEST_HOST)"; 435 | INFOPLIST_FILE = PeekViewDemoTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.ichigo.PeekViewDemoTests; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 5.0; 440 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PeekViewDemo.app/PeekViewDemo"; 441 | }; 442 | name = Release; 443 | }; 444 | /* End XCBuildConfiguration section */ 445 | 446 | /* Begin XCConfigurationList section */ 447 | 2C55530B1C6C529700D9C815 /* Build configuration list for PBXProject "PeekViewDemo" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | 2C55532B1C6C529700D9C815 /* Debug */, 451 | 2C55532C1C6C529700D9C815 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | 2C55532D1C6C529700D9C815 /* Build configuration list for PBXNativeTarget "PeekViewDemo" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | 2C55532E1C6C529700D9C815 /* Debug */, 460 | 2C55532F1C6C529700D9C815 /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | 2C5553301C6C529700D9C815 /* Build configuration list for PBXNativeTarget "PeekViewDemoTests" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | 2C5553311C6C529700D9C815 /* Debug */, 469 | 2C5553321C6C529700D9C815 /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | /* End XCConfigurationList section */ 475 | }; 476 | rootObject = 2C5553081C6C529700D9C815 /* Project object */; 477 | } 478 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PeekViewDemo 4 | // 5 | // Created by Huong Do on 2/11/16. 6 | // Copyright © 2016 Huong Do. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/appletv.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "appletv_medium_2x.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/appletv.imageset/appletv_medium_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/PeekViewDemo/PeekViewDemo/Assets.xcassets/appletv.imageset/appletv_medium_2x.jpg -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/ipad.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ipad_pro_medium_2x.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/ipad.imageset/ipad_pro_medium_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/PeekViewDemo/PeekViewDemo/Assets.xcassets/ipad.imageset/ipad_pro_medium_2x.jpg -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/iphone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "iphone-6s-change_medium_2x.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/iphone.imageset/iphone-6s-change_medium_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/PeekViewDemo/PeekViewDemo/Assets.xcassets/iphone.imageset/iphone-6s-change_medium_2x.jpg -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/macbook.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "macbook_medium_2x.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/macbook.imageset/macbook_medium_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/PeekViewDemo/PeekViewDemo/Assets.xcassets/macbook.imageset/macbook_medium_2x.jpg -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/watch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "watch_medium_2x.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/Assets.xcassets/watch.imageset/watch_medium_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/PeekViewDemo/PeekViewDemo/Assets.xcassets/watch.imageset/watch_medium_2x.jpg -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/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 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/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 | 41 | 42 | 43 | 44 | 45 | 46 | 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 | 96 | 97 | 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 | 144 | 145 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // PeekViewDemo 4 | // 5 | // Created by Huong Do on 2/11/16. 6 | // Copyright © 2016 Huong Do. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DemoViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIViewControllerPreviewingDelegate { 12 | 13 | @IBOutlet weak var collectionView: UICollectionView! 14 | 15 | let imageNames = ["iphone", "ipad", "watch", "macbook", "appletv"] 16 | let showDetailSegue = "showDetail" 17 | var forceTouchAvailable = false 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | forceTouchAvailable = false 22 | } 23 | 24 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 25 | if segue.identifier == showDetailSegue && sender is UICollectionViewCell { 26 | if let indexPath = collectionView.indexPath(for: sender as! UICollectionViewCell) { 27 | let imageName = imageNames[(indexPath as NSIndexPath).item] 28 | let controller = segue.destination as! DetailViewController 29 | controller.imageName = imageName 30 | } 31 | } 32 | } 33 | 34 | // MARK - UIViewControllerPreviewingDelegate 35 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { 36 | guard #available(iOS 9.0, *) else { 37 | return nil 38 | } 39 | 40 | let indexPath = collectionView.indexPathForItem(at: collectionView.convert(location, from:view)) 41 | if let indexPath = indexPath { 42 | let imageName = imageNames[(indexPath as NSIndexPath).item] 43 | if let cell = collectionView.cellForItem(at: indexPath) { 44 | previewingContext.sourceRect = cell.frame 45 | 46 | let controller = storyboard?.instantiateViewController(withIdentifier: "miniDetailController") as! DetailViewController 47 | controller.imageName = imageName 48 | return controller 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { 56 | 57 | let controller = storyboard?.instantiateViewController(withIdentifier: "detailController") as! DetailViewController 58 | controller.imageName = (viewControllerToCommit as! DetailViewController).imageName 59 | 60 | navigationController?.pushViewController(controller, animated: true) 61 | } 62 | 63 | // MARK - UICollectionViewDatasource 64 | func numberOfSections(in collectionView: UICollectionView) -> Int { 65 | return 1 66 | } 67 | 68 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 69 | return imageNames.count 70 | } 71 | 72 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 73 | let cellIdentifier = "photoCell" 74 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) 75 | 76 | let imageView = cell.viewWithTag(1) as! UIImageView 77 | let imageName = imageNames[(indexPath as NSIndexPath).item] 78 | imageView.image = UIImage(named: imageName) 79 | 80 | return cell 81 | } 82 | 83 | // MARK - UICollectionViewDelegate 84 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 85 | if forceTouchAvailable == false { 86 | let gesture = UILongPressGestureRecognizer(target: self, action: #selector(DemoViewController.longPressCell(_:))) 87 | gesture.minimumPressDuration = 0.5 88 | cell.addGestureRecognizer(gesture) 89 | } 90 | } 91 | 92 | @objc func longPressCell(_ gestureRecognizer: UILongPressGestureRecognizer) { 93 | 94 | if let cell = gestureRecognizer.view as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: cell) { 95 | let imageName = imageNames[(indexPath as NSIndexPath).item] 96 | let controller = storyboard?.instantiateViewController(withIdentifier: "miniDetailController") as! DetailViewController 97 | controller.imageName = imageName 98 | 99 | // you can set different frame for each peek view here 100 | let frame = CGRect(x: 15, y: (screenHeight - 300)/2, width: screenWidth - 30, height: 300) 101 | 102 | let options = [ 103 | PeekViewAction(title: "Option 1", style: .destructive), 104 | PeekViewAction(title: "Option 2", style: .default), 105 | PeekViewAction(title: "Option 3", style: .selected) ] 106 | PeekView().viewForController(parentViewController: self, contentViewController: controller, expectedContentViewFrame: frame, fromGesture: gestureRecognizer, shouldHideStatusBar: true, menuOptions: options, completionHandler: { optionIndex in 107 | switch optionIndex { 108 | case 0: 109 | print("Option 1 selected") 110 | case 1: 111 | print("Option 2 selected") 112 | case 2: 113 | print("Option 3 selected") 114 | default: 115 | break 116 | } 117 | }, dismissHandler: { 118 | print("Peekview dismissed!") 119 | }) 120 | } 121 | } 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // QuickActionViewDemo 4 | // 5 | // Created by Huong Do on 2/9/16. 6 | // Copyright © 2016 Huong Do. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | var imageName: String! 14 | 15 | @IBOutlet weak var detailImageView: UIImageView! 16 | @IBOutlet weak var titleLabel: UILabel! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | title = "Details" 21 | detailImageView.image = UIImage(named: imageName) 22 | titleLabel.text = imageName 23 | 24 | } 25 | 26 | @available(iOS 9.0, *) 27 | override var previewActionItems : [UIPreviewActionItem] { 28 | let dummyAction = UIPreviewAction(title: "3D Touch is Awesome!", style: .default, handler: { action, previewViewController in 29 | print("Action selected!") 30 | }) 31 | 32 | return [dummyAction] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemo/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemoTests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PeekViewDemo/PeekViewDemoTests/PeekViewDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeekViewDemoTests.swift 3 | // PeekViewDemoTests 4 | // 5 | // Created by Huong Do on 2/11/16. 6 | // Copyright © 2016 Huong Do. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PeekViewDemo 11 | 12 | class PeekViewDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PeekView 2 | When implementing peek, pop and preview actions with 3D Touch, you may want to support such features for users accessing your app from older devices that don't provide 3D Touch capibility. PeekView hence can be used as an alternative in such case. 3 | 4 | ![Preview](https://github.com/itsmeichigo/PeekView/blob/master/peekview.gif) 5 | ![Screenshot](https://github.com/itsmeichigo/PeekView/blob/master/screenshot.png) 6 | 7 | (Please ignore the low resolution of the GIF. Try the demo for actual experience.) 8 | 9 | ## Note 10 | 11 | Things that need improving 12 | - Better solution for Objective-C integration (the current is sort of hacky!) 13 | - Better documenting in code 14 | - Content view panned: Smoother animation 15 | - Action style: Selected functionality 16 | - More customizable UI if needed (requests are welcome) 17 | - Bug fixes if any 18 | 19 | ## Requirements 20 | 21 | - iOS 8 and later 22 | - Swift 5 & Xcode 10.2 23 | - If you wish to work with Swift 2.3, check out branch `swift23` 24 | 25 | ## Getting Started 26 | 27 | #### Install using CocoaPods 28 | 29 | Just add the following line in to your pod file: 30 | 31 | pod 'PeekView' 32 | 33 | #### Manual Install 34 | 35 | Drag and drop folder named `Source` in your project and you're done. 36 | 37 | ### Usage 38 | 39 | - Add `UILongPressGestureRecognizer` to the view you want to peek (i.e table view cell, image, hypertext, etc.) 40 | - Create a `UIViewController` instance as the content of your peek view; then set your desired frame for the content view. It's recommended to leave a 15px padding for both left and right margin of your content view. 41 | - If you want to include preview actions, prepare an array containing title of the buttons and its preview style. Don't forget to prepare completion handlers for when each button is tapped. 42 | 43 | Sample snippet: 44 | 45 | ```Swift 46 | let options = [ 47 | PeekViewAction(title: "Option 1", style: .Destructive), 48 | PeekViewAction(title: "Option 2", style: .Default), 49 | PeekViewAction(title: "Option 3", style: .Selected) ] 50 | PeekView().viewForController( 51 | parentViewController: self, 52 | contentViewController: controller, 53 | expectedContentViewFrame: frame, 54 | fromGesture: gestureRecognizer, 55 | shouldHideStatusBar: true, 56 | withOptions: options, 57 | completionHandler: { optionIndex in 58 | switch optionIndex { 59 | case 0: 60 | print("Option 1 selected") 61 | case 1: 62 | print("Option 2 selected") 63 | case 2: 64 | print("Option 3 selected") 65 | default: 66 | break 67 | } 68 | }) 69 | ``` 70 | 71 | Be sure to check out the demo code for better understanding of the usage. 72 | 73 | As for Objective-C integration, a slightly different static function should be used, in which the `menuOptions` array is expected to be an `NSArray` of `NSDictionary`'s. Please check the snippet below: 74 | 75 | ```Objc 76 | 77 | NSArray *options = @[@{@"Option 1": @(PeekViewActionStyleDefault)}, 78 | @{@"Option 2": @(PeekViewActionStyleDestructive)}]; 79 | 80 | UIViewController *contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"previewVC"]; 81 | 82 | [PeekView viewForControllerWithParentViewController:self 83 | contentViewController:contentViewController 84 | expectedContentViewFrame:CGRectMake(0, 0, 280, 400) 85 | fromGesture:gesture 86 | shouldHideStatusBar:YES 87 | withOptions:options 88 | completionHandler:nil]; 89 | ``` 90 | 91 | ### ARC 92 | 93 | PeekView uses ARC. If you are using PeekView in a non-arc project, you 94 | will need to set a `-fobjc-arc` compiler flag on every PeekView source files. To set a 95 | compiler flag in Xcode, go to your active target and select the "Build Phases" tab. Then select 96 | PeekView source files, press Enter, insert -fobjc-arc and then "Done" to enable ARC 97 | for PeekView. 98 | 99 | ## Contributing 100 | 101 | Contributions for bug fixing or improvements are welcome. Feel free to submit a pull request. 102 | 103 | ## Licence 104 | 105 | PeekView is available under the MIT license. See the LICENSE file for more info. 106 | -------------------------------------------------------------------------------- /Source/Assets/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/Source/Assets/arrow@2x.png -------------------------------------------------------------------------------- /Source/Assets/arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/Source/Assets/arrow@3x.png -------------------------------------------------------------------------------- /Source/Assets/checked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/Source/Assets/checked@2x.png -------------------------------------------------------------------------------- /Source/Assets/checked@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/Source/Assets/checked@3x.png -------------------------------------------------------------------------------- /Source/PeekView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeekView.swift 3 | // PeekViewDemo 4 | // 5 | // Created by Huong Do on 2/11/16. 6 | // Copyright © 2016 Huong Do. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let screenWidth = UIScreen.main.bounds.size.width 12 | let screenHeight = UIScreen.main.bounds.size.height 13 | let peekViewTag = 1929 14 | let tickImageViewTag = 1930 15 | let buttonVerticalPadding = CGFloat(15) 16 | var fromTouchToContentCenter = CGFloat(0) 17 | 18 | @objc public enum PeekViewActionStyle : Int { 19 | case `default` 20 | case selected 21 | case destructive 22 | } 23 | 24 | public struct PeekViewAction { 25 | var title: String 26 | var style: PeekViewActionStyle 27 | 28 | public init(title: String, style: PeekViewActionStyle){ 29 | self.title = title 30 | self.style = style 31 | } 32 | } 33 | 34 | @objc open class PeekView: UIView { 35 | 36 | var shouldToggleHidingStatusBar = false 37 | var contentView: UIView? 38 | var buttonHolderView: UIView? 39 | var completionHandler: ((Int) -> Void)? 40 | var dismissHandler: (()->Void)? 41 | 42 | var arrowImageView: UIImageView? 43 | 44 | /** 45 | * Helper class function to support objective-c projects 46 | * Since struct cannot be bridged to obj-c, user can input an NSArray of NSDictionary objects, 47 | * whose key will be treated as option title and value is an NSNumber of PeekViewActionStyle. 48 | * (More delicate solution to be found later :[ ) 49 | */ 50 | @objc open class func viewForController( 51 | parentViewController: UIViewController, 52 | contentViewController: UIViewController, 53 | expectedContentViewFrame: CGRect, 54 | fromGesture: UILongPressGestureRecognizer, 55 | shouldHideStatusBar: Bool, 56 | menuOptions: NSArray?, 57 | completionHandler: ((Int) -> Void)?, 58 | dismissHandler: (()->Void)?) { 59 | 60 | var options: [PeekViewAction]? = nil 61 | if let menuOptions = menuOptions { 62 | options = [] 63 | for option in menuOptions { 64 | if let dictionary = option as? NSDictionary, 65 | let title = dictionary.allKeys[0] as? NSString, 66 | let styleNumber = dictionary[title] as? NSNumber, 67 | let style = PeekViewActionStyle(rawValue: styleNumber.intValue) { 68 | options?.append(PeekViewAction(title: title as String, style: style)) 69 | } 70 | } 71 | } 72 | 73 | PeekView().viewForController( 74 | parentViewController: parentViewController, 75 | contentViewController: contentViewController, 76 | expectedContentViewFrame: expectedContentViewFrame, 77 | fromGesture: fromGesture, 78 | shouldHideStatusBar: shouldHideStatusBar, 79 | menuOptions: options, 80 | completionHandler: completionHandler, 81 | dismissHandler: dismissHandler) 82 | } 83 | 84 | /** 85 | * 86 | */ 87 | open func viewForController( 88 | parentViewController: UIViewController, 89 | contentViewController: UIViewController, 90 | expectedContentViewFrame: CGRect, 91 | fromGesture: UILongPressGestureRecognizer, 92 | shouldHideStatusBar: Bool, 93 | menuOptions: [PeekViewAction]?=nil, 94 | completionHandler: ((Int) -> Void)?=nil, 95 | dismissHandler: (()->Void)?=nil) { 96 | 97 | let window = UIApplication.shared.keyWindow! 98 | 99 | switch fromGesture.state { 100 | case .began: 101 | let peekView = PeekView(frame: window.frame) 102 | peekView.configureView(viewController: contentViewController, 103 | subviewFrame: expectedContentViewFrame, 104 | shouldHideStatusBar: shouldHideStatusBar, 105 | options: menuOptions, 106 | completionHandler: completionHandler, 107 | dismissHandler: dismissHandler) 108 | peekView.tag = peekViewTag 109 | window.addSubview(peekView) 110 | 111 | parentViewController.addChild(contentViewController) 112 | contentViewController.didMove(toParent: parentViewController) 113 | 114 | // DuyNT: Calculate distance from touch location to vertical center point of contentView, and when the touch location moves as the user swiping his hand, vertical center point of contentView will be recalculated, resulting in a nicer visual which contentView center does not necessary be aligned with user's finger as in original solution. 115 | if let view = window.viewWithTag(peekViewTag) as? PeekView { 116 | let pointOfHand = fromGesture.location(in: view.superview).y 117 | fromTouchToContentCenter = pointOfHand - screenHeight / 2 118 | } 119 | 120 | case .changed: 121 | if let view = window.viewWithTag(peekViewTag) as? PeekView { 122 | 123 | // DuyNT: Here we use the number calculated before to get 'better' center position of contentView 124 | let pointOfHand = fromGesture.location(in: view.superview!).y 125 | var centerOfContent = CGFloat(0) 126 | 127 | centerOfContent = pointOfHand - fromTouchToContentCenter 128 | 129 | view.updateContentViewFrame(centerOfContent) 130 | } 131 | case .ended: 132 | fromTouchToContentCenter = CGFloat(0) 133 | 134 | if let view = window.viewWithTag(peekViewTag) as? PeekView { 135 | if let buttonHolderView = view.buttonHolderView, let contentView = view.contentView { 136 | if buttonHolderView.frame.minY <= view.frame.maxY - buttonHolderView.frame.height - buttonVerticalPadding { 137 | var frame = contentView.frame 138 | frame.origin.y = buttonHolderView.frame.minY - contentView.frame.height - buttonVerticalPadding 139 | UIView.animate(withDuration: 0.3, animations: { () -> Void in 140 | contentView.frame = frame 141 | }) 142 | } else { 143 | var frame = buttonHolderView.frame 144 | frame.origin.y = view.frame.maxY 145 | UIView.animate(withDuration: 0.3, animations: { () -> Void in 146 | buttonHolderView.frame = frame 147 | contentView.center = CGPoint(x: view.frame.width/2, y: view.frame.height/2) 148 | // move arrow along with content view 149 | if view.arrowImageView != nil { 150 | var arrowCenterPoint = view.arrowImageView!.center 151 | arrowCenterPoint.y = contentView.frame.minY - 17 152 | view.arrowImageView!.center = arrowCenterPoint 153 | view.arrowImageView!.alpha = 0 154 | } 155 | }, completion: { completed in 156 | view.dismissView() 157 | }) 158 | } 159 | } else { 160 | view.dismissView() 161 | } 162 | } 163 | default: 164 | fromTouchToContentCenter = CGFloat(0) 165 | break 166 | } 167 | 168 | } 169 | 170 | func configureView(viewController: UIViewController, subviewFrame: CGRect, shouldHideStatusBar: Bool, options: [PeekViewAction]?=nil, completionHandler: ((Int) -> Void)?=nil, dismissHandler: (()->Void)?=nil) { 171 | 172 | self.shouldToggleHidingStatusBar = shouldHideStatusBar 173 | self.completionHandler = completionHandler 174 | self.dismissHandler = dismissHandler 175 | 176 | if shouldToggleHidingStatusBar == true { 177 | UIApplication.shared.isStatusBarHidden = true 178 | } 179 | 180 | // Configure vibrancy 181 | let blurEffect = UIBlurEffect(style: .light) 182 | let blurEffectView = UIVisualEffectView(effect: blurEffect) 183 | blurEffectView.frame = self.bounds 184 | 185 | let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) 186 | let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) 187 | vibrancyEffectView.frame = self.bounds 188 | let vibrancyContentView = UIView(frame: self.bounds) 189 | vibrancyContentView.backgroundColor = UIColor.white 190 | vibrancyEffectView.contentView.addSubview(vibrancyContentView) 191 | blurEffectView.contentView.addSubview(vibrancyEffectView) 192 | 193 | self.addSubview(blurEffectView) 194 | 195 | // Configure content view 196 | contentView = viewController.view 197 | contentView!.frame = subviewFrame 198 | contentView!.layer.masksToBounds = true 199 | contentView!.layer.cornerRadius = 7 200 | contentView!.alpha = 0 201 | self.addSubview(contentView!) 202 | 203 | // Add gesture 204 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(PeekView.dismissView)) 205 | self.addGestureRecognizer(tapGestureRecognizer) 206 | 207 | let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(PeekView.contentViewPanned(_:))) 208 | contentView!.addGestureRecognizer(panGestureRecognizer) 209 | 210 | UIView.animate(withDuration: 0.3, animations: { () -> Void in 211 | self.contentView!.alpha = 1 212 | }) 213 | 214 | // If options are provided: configure buttons 215 | if let options = options { 216 | 217 | guard options.count > 0 else { 218 | return 219 | } 220 | 221 | // Add arrow image 222 | arrowImageView = UIImageView(frame: CGRect(x: screenWidth/2 - 18, y: contentView!.frame.minY - 25, width: 36, height: 11)) 223 | let bundle = Bundle(for: self.classForCoder) 224 | arrowImageView!.image = UIImage(named: "arrow", in: bundle, compatibleWith: nil) 225 | self.addSubview(arrowImageView!) 226 | 227 | let cornerRadius = CGFloat(10) 228 | let buttonHeight = CGFloat(58) 229 | 230 | buttonHolderView = UIView(frame: CGRect(x: subviewFrame.minX, y: self.frame.maxY, width: subviewFrame.width, height: buttonHeight*CGFloat(options.count))) 231 | buttonHolderView!.layer.backgroundColor = UIColor.white.cgColor 232 | buttonHolderView!.layer.cornerRadius = cornerRadius 233 | buttonHolderView!.layer.masksToBounds = true 234 | self.addSubview(buttonHolderView!) 235 | 236 | for index in 0.. Void in 281 | buttonHolderView.frame = buttonHolderViewFrame 282 | contentView.frame = contentViewFrame 283 | }, completion: { completed in 284 | self.dismissView() 285 | }) 286 | } else { 287 | dismissView() 288 | } 289 | } 290 | 291 | @objc func dismissView() { 292 | if let contentView = contentView { 293 | if self.shouldToggleHidingStatusBar == true { 294 | UIApplication.shared.setStatusBarHidden(false, with: .fade) 295 | } 296 | UIView.animate(withDuration: 0.3, animations: { () -> Void in 297 | contentView.alpha = 0 298 | }, completion: { completion in 299 | self.dismissHandler?() 300 | self.removeFromSuperview() 301 | }) 302 | } 303 | } 304 | 305 | func updateContentViewFrame(_ frameY: CGFloat) { 306 | if let contentView = contentView { 307 | var contentCenterPoint = contentView.center 308 | contentCenterPoint.y = frameY 309 | contentView.center = contentCenterPoint 310 | 311 | // move arrow along with content view 312 | if arrowImageView != nil { 313 | var arrowCenterPoint = arrowImageView!.center 314 | arrowCenterPoint.y = contentView.frame.minY - 17 315 | arrowImageView!.center = arrowCenterPoint 316 | } 317 | 318 | if let buttonHolderView = buttonHolderView { 319 | if contentView.frame.maxY < self.frame.maxY - buttonHolderView.frame.height - buttonVerticalPadding*2 { 320 | // if option buttons are visible entirely 321 | var frame = buttonHolderView.frame 322 | frame.origin.y = self.frame.maxY - buttonHolderView.frame.height - buttonVerticalPadding 323 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 324 | buttonHolderView.frame = frame 325 | self.arrowImageView?.alpha = 0 326 | }) 327 | } else if buttonHolderView.frame.minY < self.frame.maxY && contentView.frame.maxY < self.frame.maxY - buttonHolderView.frame.height - buttonVerticalPadding { 328 | // if option buttons are visible partially 329 | var frame = buttonHolderView.frame 330 | frame.origin.y = contentView.frame.maxY + buttonVerticalPadding 331 | buttonHolderView.frame = frame 332 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 333 | self.arrowImageView?.alpha = 0 334 | }) 335 | } else { 336 | // hide option buttons 337 | var frame = buttonHolderView.frame 338 | frame.origin.y = self.frame.maxY 339 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 340 | buttonHolderView.frame = frame 341 | self.arrowImageView?.alpha = 1 342 | }) 343 | 344 | } 345 | } 346 | } 347 | } 348 | 349 | @objc func contentViewPanned(_ gestureRecognizer: UIPanGestureRecognizer) { 350 | switch gestureRecognizer.state { 351 | case .ended: 352 | if let buttonHolderView = buttonHolderView, let contentView = contentView { 353 | if buttonHolderView.frame.minY <= frame.maxY - buttonHolderView.frame.height - buttonVerticalPadding { 354 | var frame = contentView.frame 355 | frame.origin.y = buttonHolderView.frame.minY - contentView.frame.height - buttonVerticalPadding 356 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 357 | contentView.frame = frame 358 | }) 359 | } else { 360 | var frame = buttonHolderView.frame 361 | frame.origin.y = self.frame.maxY 362 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 363 | buttonHolderView.frame = frame 364 | contentView.center = CGPoint(x: self.frame.width/2, y: self.frame.height/2) 365 | // move arrow along with content view 366 | if self.arrowImageView != nil { 367 | var arrowCenterPoint = self.arrowImageView!.center 368 | arrowCenterPoint.y = contentView.frame.minY - 17 369 | self.arrowImageView!.center = arrowCenterPoint 370 | self.arrowImageView!.alpha = 0 371 | } 372 | 373 | }, completion: { completed in 374 | self.dismissView() 375 | }) 376 | } 377 | } else { 378 | dismissView() 379 | } 380 | default: 381 | let pointOfHand = gestureRecognizer.location(in: self).y 382 | var centerOfContent = CGFloat(0) 383 | centerOfContent = pointOfHand - fromTouchToContentCenter 384 | 385 | updateContentViewFrame(centerOfContent) 386 | } 387 | 388 | } 389 | 390 | } 391 | -------------------------------------------------------------------------------- /peekview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/peekview.gif -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmeichigo/PeekView/a926b0cbd05025bb3156a2588114b3e469a4e8c4/screenshot.png --------------------------------------------------------------------------------