├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── STPopupPreview.podspec ├── STPopupPreview.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── STPopupPreview.xcscheme └── xcuserdata │ └── kevinlin.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── STPopupPreview ├── Info.plist ├── STPopupPreview.h ├── STPopupPreviewRecognizer.h ├── STPopupPreviewRecognizer.m ├── UIView+STPopupPreview.h └── UIView+STPopupPreview.m └── STPopupPreviewExample ├── STPopupPreviewExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── kevinlin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── STPopupPreviewExample ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── ExampleData.json ├── ImageLoader.h ├── ImageLoader.m ├── Info.plist ├── PreviewViewController.h ├── PreviewViewController.m ├── ViewController.h ├── ViewController.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | 5 | # Icon must end with two \r 6 | Icon 7 | 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | 20 | # Directories potentially created on remote AFP share 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | 27 | # Xcode 28 | # 29 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 30 | 31 | ## Build generated 32 | build/ 33 | DerivedData/ 34 | 35 | ## Various settings 36 | *.pbxuser 37 | !default.pbxuser 38 | *.mode1v3 39 | !default.mode1v3 40 | *.mode2v3 41 | !default.mode2v3 42 | *.perspectivev3 43 | !default.perspectivev3 44 | xcuserdata/ 45 | 46 | ## Other 47 | *.moved-aside 48 | *.xccheckout 49 | *.xcscmblueprint 50 | 51 | ## Obj-C/Swift specific 52 | *.hmap 53 | *.ipa 54 | 55 | # CocoaPods 56 | # 57 | # We recommend against adding the Pods directory to your .gitignore. However 58 | # you should judge for yourself, the pros and cons are mentioned at: 59 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 60 | # 61 | Pods/ 62 | 63 | # Carthage 64 | # 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build 69 | 70 | # fastlane 71 | # 72 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 73 | # screenshots whenever they are needed. 74 | # For more information about the recommended setup visit: 75 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 76 | 77 | fastlane/report.xml 78 | fastlane/screenshots -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "STPopup"] 2 | path = STPopup 3 | url = https://github.com/kevin0571/STPopup 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9 3 | script: 4 | - xcodebuild -project STPopupPreview.xcodeproj -scheme STPopupPreview -sdk iphonesimulator 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kevin Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STPopupPreview ![CI Status](https://travis-ci.org/kevin0571/STPopupPreview.svg?branch=master) ![Version](http://img.shields.io/cocoapods/v/STPopupPreview.svg?style=flag) ![License](https://img.shields.io/cocoapods/l/STPopupPreview.svg?style=flag) 2 | **STPopupPreview** uses long press gesture to enable quick preview of a page on non 3D Touch devices. Preview actions are also supported. This idea is inspired by Instagram. 3 | 4 | It is built on top of of [STPopup](http://github.com/kevin0571/STPopup)(a library provides STPopupController, which works just like UINavigationController in popup style). Both STPopup and STPopupPreview support iOS 7+. 5 | 6 | ## Demo 7 | 8 | ![STPopupPreviewDemo](https://cloud.githubusercontent.com/assets/1491282/15470641/4cf17556-2124-11e6-885b-d2242de06974.gif) 9 | 10 | ## Features 11 | * Long press to preview, release to dismiss. 12 | * Slide up to show preview actions. 13 | * Easy integration. 14 | 15 | ## Get Started 16 | **CocoaPods** 17 | ```ruby 18 | platform: ios, '7.0' 19 | pod 'STPopupPreview' 20 | ``` 21 | **Carthage** 22 | ```ruby 23 | github "kevin0571/STPopupPreview" 24 | ``` 25 | *Don't forget to drag both STPopupPreview.framework and STPopup.framework into linked frameworks. 26 | ## Usage 27 | 28 | ### Import header file 29 | ```objc 30 | #import 31 | ``` 32 | 33 | ### Attach popup preview recognizer to view 34 | ```objc 35 | CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([CollectionViewCell class]) forIndexPath:indexPath]; 36 | if (!cell.popupPreviewRecognizer) { 37 | cell.popupPreviewRecognizer = [[STPopupPreviewRecognizer alloc] initWithDelegate:self]; 38 | } 39 | ``` 40 | 41 | ### Implement STPopupPreviewRecognizerDelegate 42 | 43 | Return the preview view controller. The preview view controller should have "contentSizeInPopup" set before its "viewDidLoad" called. More about this please read the document of [STPopup](http://github.com/kevin0571/STPopup). 44 | 45 | ```objc 46 | - (UIViewController *)previewViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 47 | { 48 | if (![popupPreviewRecognizer.view isKindOfClass:[CollectionViewCell class]]) { 49 | return nil; 50 | } 51 | 52 | CollectionViewCell *cell = popupPreviewRecognizer.view; 53 | 54 | PreviewViewController *previewViewController = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([PreviewViewController class])]; 55 | previewViewController.data = cell.data; 56 | previewViewController.placeholderImage = cell.imageView.image; 57 | return previewViewController; 58 | } 59 | ``` 60 | 61 | Return a view controller to present the preview view controller. Most of the time it will be the current view controller. 62 | 63 | ```objc 64 | - (UIViewController *)presentingViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 65 | { 66 | return self; 67 | } 68 | ``` 69 | 70 | Return the preview actions you want to show when slides up. It can be nil if you don't have any preview actions. 71 | 72 | ```objc 73 | - (NSArray *)previewActionsForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 74 | { 75 | return @[ [STPopupPreviewAction actionWithTitle:@"Like" style:STPopupPreviewActionStyleDefault handler:^(STPopupPreviewAction *action, UIViewController *previewViewController) { 76 | // Action handler 77 | }] ]; 78 | } 79 | ``` 80 | 81 | ### Enable STPopupPreview only if 3D Touch is not available 82 | ```objc 83 | BOOL isForceTouchAvailable = [self respondsToSelector:@selector(traitCollection)] && 84 | [self.traitCollection respondsToSelector:@selector(forceTouchCapability)] && 85 | self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable; 86 | if (!isForceTouchAvailable) { 87 | if (!cell.popupPreviewRecognizer) { 88 | cell.popupPreviewRecognizer = [[STPopupPreviewRecognizer alloc] initWithDelegate:self]; 89 | } 90 | } 91 | ``` 92 | 93 | ## Issues & Contact 94 | * If you have any question regarding the usage, please refer to the example project for more details. 95 | * If you find any bug, please submit an **issue**. 96 | -------------------------------------------------------------------------------- /STPopupPreview.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "STPopupPreview" 3 | s.version = "1.0.5" 4 | s.summary = "An alternative peek preview for non 3D Touch devices. Inspired by Instagram." 5 | 6 | s.description = <<-DESC 7 | - STPopupPreview uses long press gesture to enable quick preview of a page on non 3D Touch devices. Preview actions are also supported. This idea is inspired by Instagram. 8 | - It is built on top of of STPopup(a library provides STPopupController, which works just like UINavigationController in popup style). Both STPopup and STPopupPreview support iOS 7+. 9 | DESC 10 | 11 | s.homepage = "https://github.com/kevin0571/STPopupPreview" 12 | s.license = { :type => "MIT", :file => "LICENSE" } 13 | s.author = { "Kevin Lin" => "kevin_lyn@outlook.com" } 14 | 15 | s.platform = :ios, "7.0" 16 | s.source = { :git => "https://github.com/kevin0571/STPopupPreview.git", :tag => s.version } 17 | 18 | s.source_files = "STPopupPreview/*.{h,m}" 19 | s.public_header_files = "STPopupPreview/*.h" 20 | s.dependency 'STPopup', '~> 1.7' 21 | end 22 | -------------------------------------------------------------------------------- /STPopupPreview.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 769647BC1CF16C80002D126E /* STPopupPreview.h in Headers */ = {isa = PBXBuildFile; fileRef = 769647BB1CF16C80002D126E /* STPopupPreview.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 769647CB1CF16CCF002D126E /* STPopup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 769647C81CF16CC5002D126E /* STPopup.framework */; }; 12 | 769647FF1CF16F83002D126E /* STPopupPreviewRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 769647FD1CF16F83002D126E /* STPopupPreviewRecognizer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 769648001CF16F83002D126E /* STPopupPreviewRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 769647FE1CF16F83002D126E /* STPopupPreviewRecognizer.m */; }; 14 | 769648031CF16FB3002D126E /* UIView+STPopupPreview.h in Headers */ = {isa = PBXBuildFile; fileRef = 769648011CF16FB3002D126E /* UIView+STPopupPreview.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 769648041CF16FB3002D126E /* UIView+STPopupPreview.m in Sources */ = {isa = PBXBuildFile; fileRef = 769648021CF16FB3002D126E /* UIView+STPopupPreview.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | 769647C71CF16CC5002D126E /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = 769647C31CF16CC4002D126E /* STPopup.xcodeproj */; 22 | proxyType = 2; 23 | remoteGlobalIDString = 76D822381C1DB5E400FCF988; 24 | remoteInfo = STPopup; 25 | }; 26 | 769647C91CF16CCC002D126E /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 769647C31CF16CC4002D126E /* STPopup.xcodeproj */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 76D822371C1DB5E400FCF988; 31 | remoteInfo = STPopup; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 769647B81CF16C80002D126E /* STPopupPreview.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = STPopupPreview.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 769647BB1CF16C80002D126E /* STPopupPreview.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPopupPreview.h; sourceTree = ""; }; 38 | 769647BD1CF16C80002D126E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 769647C31CF16CC4002D126E /* STPopup.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = STPopup.xcodeproj; path = STPopup/STPopup.xcodeproj; sourceTree = ""; }; 40 | 769647FD1CF16F83002D126E /* STPopupPreviewRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPopupPreviewRecognizer.h; sourceTree = ""; }; 41 | 769647FE1CF16F83002D126E /* STPopupPreviewRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPopupPreviewRecognizer.m; sourceTree = ""; }; 42 | 769648011CF16FB3002D126E /* UIView+STPopupPreview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+STPopupPreview.h"; sourceTree = ""; }; 43 | 769648021CF16FB3002D126E /* UIView+STPopupPreview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+STPopupPreview.m"; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 769647B41CF16C80002D126E /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | 769647CB1CF16CCF002D126E /* STPopup.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 769647AE1CF16C80002D126E = { 59 | isa = PBXGroup; 60 | children = ( 61 | 769647C31CF16CC4002D126E /* STPopup.xcodeproj */, 62 | 769647BA1CF16C80002D126E /* STPopupPreview */, 63 | 769647B91CF16C80002D126E /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | 769647B91CF16C80002D126E /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 769647B81CF16C80002D126E /* STPopupPreview.framework */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 769647BA1CF16C80002D126E /* STPopupPreview */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 769647BD1CF16C80002D126E /* Info.plist */, 79 | 769647BB1CF16C80002D126E /* STPopupPreview.h */, 80 | 769647FD1CF16F83002D126E /* STPopupPreviewRecognizer.h */, 81 | 769647FE1CF16F83002D126E /* STPopupPreviewRecognizer.m */, 82 | 769648011CF16FB3002D126E /* UIView+STPopupPreview.h */, 83 | 769648021CF16FB3002D126E /* UIView+STPopupPreview.m */, 84 | ); 85 | path = STPopupPreview; 86 | sourceTree = ""; 87 | }; 88 | 769647C41CF16CC4002D126E /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 769647C81CF16CC5002D126E /* STPopup.framework */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXHeadersBuildPhase section */ 99 | 769647B51CF16C80002D126E /* Headers */ = { 100 | isa = PBXHeadersBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | 769647FF1CF16F83002D126E /* STPopupPreviewRecognizer.h in Headers */, 104 | 769648031CF16FB3002D126E /* UIView+STPopupPreview.h in Headers */, 105 | 769647BC1CF16C80002D126E /* STPopupPreview.h in Headers */, 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | /* End PBXHeadersBuildPhase section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 769647B71CF16C80002D126E /* STPopupPreview */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 769647C01CF16C80002D126E /* Build configuration list for PBXNativeTarget "STPopupPreview" */; 115 | buildPhases = ( 116 | 769647B31CF16C80002D126E /* Sources */, 117 | 769647B41CF16C80002D126E /* Frameworks */, 118 | 769647B51CF16C80002D126E /* Headers */, 119 | 769647B61CF16C80002D126E /* Resources */, 120 | ); 121 | buildRules = ( 122 | ); 123 | dependencies = ( 124 | 769647CA1CF16CCC002D126E /* PBXTargetDependency */, 125 | ); 126 | name = STPopupPreview; 127 | productName = STPopupPreview; 128 | productReference = 769647B81CF16C80002D126E /* STPopupPreview.framework */; 129 | productType = "com.apple.product-type.framework"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | 769647AF1CF16C80002D126E /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | LastUpgradeCheck = 1010; 138 | ORGANIZATIONNAME = Sth4Me; 139 | TargetAttributes = { 140 | 769647B71CF16C80002D126E = { 141 | CreatedOnToolsVersion = 7.3.1; 142 | }; 143 | }; 144 | }; 145 | buildConfigurationList = 769647B21CF16C80002D126E /* Build configuration list for PBXProject "STPopupPreview" */; 146 | compatibilityVersion = "Xcode 3.2"; 147 | developmentRegion = English; 148 | hasScannedForEncodings = 0; 149 | knownRegions = ( 150 | en, 151 | ); 152 | mainGroup = 769647AE1CF16C80002D126E; 153 | productRefGroup = 769647B91CF16C80002D126E /* Products */; 154 | projectDirPath = ""; 155 | projectReferences = ( 156 | { 157 | ProductGroup = 769647C41CF16CC4002D126E /* Products */; 158 | ProjectRef = 769647C31CF16CC4002D126E /* STPopup.xcodeproj */; 159 | }, 160 | ); 161 | projectRoot = ""; 162 | targets = ( 163 | 769647B71CF16C80002D126E /* STPopupPreview */, 164 | ); 165 | }; 166 | /* End PBXProject section */ 167 | 168 | /* Begin PBXReferenceProxy section */ 169 | 769647C81CF16CC5002D126E /* STPopup.framework */ = { 170 | isa = PBXReferenceProxy; 171 | fileType = wrapper.framework; 172 | path = STPopup.framework; 173 | remoteRef = 769647C71CF16CC5002D126E /* PBXContainerItemProxy */; 174 | sourceTree = BUILT_PRODUCTS_DIR; 175 | }; 176 | /* End PBXReferenceProxy section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | 769647B61CF16C80002D126E /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | /* End PBXResourcesBuildPhase section */ 187 | 188 | /* Begin PBXSourcesBuildPhase section */ 189 | 769647B31CF16C80002D126E /* Sources */ = { 190 | isa = PBXSourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 769648041CF16FB3002D126E /* UIView+STPopupPreview.m in Sources */, 194 | 769648001CF16F83002D126E /* STPopupPreviewRecognizer.m in Sources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXSourcesBuildPhase section */ 199 | 200 | /* Begin PBXTargetDependency section */ 201 | 769647CA1CF16CCC002D126E /* PBXTargetDependency */ = { 202 | isa = PBXTargetDependency; 203 | name = STPopup; 204 | targetProxy = 769647C91CF16CCC002D126E /* PBXContainerItemProxy */; 205 | }; 206 | /* End PBXTargetDependency section */ 207 | 208 | /* Begin XCBuildConfiguration section */ 209 | 769647BE1CF16C80002D126E /* Debug */ = { 210 | isa = XCBuildConfiguration; 211 | buildSettings = { 212 | ALWAYS_SEARCH_USER_PATHS = NO; 213 | CLANG_ANALYZER_NONNULL = YES; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 215 | CLANG_CXX_LIBRARY = "libc++"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_COMMA = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 230 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 231 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 232 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 233 | CLANG_WARN_STRICT_PROTOTYPES = YES; 234 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 238 | COPY_PHASE_STRIP = NO; 239 | CURRENT_PROJECT_VERSION = 1; 240 | DEBUG_INFORMATION_FORMAT = dwarf; 241 | ENABLE_STRICT_OBJC_MSGSEND = YES; 242 | ENABLE_TESTABILITY = YES; 243 | GCC_C_LANGUAGE_STANDARD = gnu99; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PREPROCESSOR_DEFINITIONS = ( 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 255 | GCC_WARN_UNUSED_FUNCTION = YES; 256 | GCC_WARN_UNUSED_VARIABLE = YES; 257 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 258 | MTL_ENABLE_DEBUG_INFO = YES; 259 | ONLY_ACTIVE_ARCH = YES; 260 | SDKROOT = iphoneos; 261 | TARGETED_DEVICE_FAMILY = "1,2"; 262 | VERSIONING_SYSTEM = "apple-generic"; 263 | VERSION_INFO_PREFIX = ""; 264 | }; 265 | name = Debug; 266 | }; 267 | 769647BF1CF16C80002D126E /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_MODULES = YES; 275 | CLANG_ENABLE_OBJC_ARC = YES; 276 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_COMMA = YES; 279 | CLANG_WARN_CONSTANT_CONVERSION = YES; 280 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INFINITE_RECURSION = YES; 285 | CLANG_WARN_INT_CONVERSION = YES; 286 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 296 | COPY_PHASE_STRIP = NO; 297 | CURRENT_PROJECT_VERSION = 1; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu99; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | SDKROOT = iphoneos; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | VALIDATE_PRODUCT = YES; 314 | VERSIONING_SYSTEM = "apple-generic"; 315 | VERSION_INFO_PREFIX = ""; 316 | }; 317 | name = Release; 318 | }; 319 | 769647C11CF16C80002D126E /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 323 | DEFINES_MODULE = YES; 324 | DYLIB_COMPATIBILITY_VERSION = 1; 325 | DYLIB_CURRENT_VERSION = 1; 326 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 327 | INFOPLIST_FILE = STPopupPreview/Info.plist; 328 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 329 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = me.sth4.STPopupPreview; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SKIP_INSTALL = YES; 334 | }; 335 | name = Debug; 336 | }; 337 | 769647C21CF16C80002D126E /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 341 | DEFINES_MODULE = YES; 342 | DYLIB_COMPATIBILITY_VERSION = 1; 343 | DYLIB_CURRENT_VERSION = 1; 344 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 345 | INFOPLIST_FILE = STPopupPreview/Info.plist; 346 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 347 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 349 | PRODUCT_BUNDLE_IDENTIFIER = me.sth4.STPopupPreview; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | SKIP_INSTALL = YES; 352 | }; 353 | name = Release; 354 | }; 355 | /* End XCBuildConfiguration section */ 356 | 357 | /* Begin XCConfigurationList section */ 358 | 769647B21CF16C80002D126E /* Build configuration list for PBXProject "STPopupPreview" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 769647BE1CF16C80002D126E /* Debug */, 362 | 769647BF1CF16C80002D126E /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | 769647C01CF16C80002D126E /* Build configuration list for PBXNativeTarget "STPopupPreview" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 769647C11CF16C80002D126E /* Debug */, 371 | 769647C21CF16C80002D126E /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | /* End XCConfigurationList section */ 377 | }; 378 | rootObject = 769647AF1CF16C80002D126E /* Project object */; 379 | } 380 | -------------------------------------------------------------------------------- /STPopupPreview.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /STPopupPreview.xcodeproj/xcshareddata/xcschemes/STPopupPreview.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /STPopupPreview.xcodeproj/xcuserdata/kevinlin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | STPopupPreview.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 769647B71CF16C80002D126E 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /STPopupPreview/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.5 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /STPopupPreview/STPopupPreview.h: -------------------------------------------------------------------------------- 1 | // 2 | // STPopupPreview.h 3 | // STPopupPreview 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for STPopupPreview. 12 | FOUNDATION_EXPORT double STPopupPreviewVersionNumber; 13 | 14 | //! Project version string for STPopupPreview. 15 | FOUNDATION_EXPORT const unsigned char STPopupPreviewVersionString[]; 16 | 17 | #import 18 | #import -------------------------------------------------------------------------------- /STPopupPreview/STPopupPreviewRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // STPopupPreviewRecognizer.h 3 | // STPopupPreview 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSInteger, STPopupPreviewActionStyle) { 14 | /** 15 | Default action in global tint color. 16 | */ 17 | STPopupPreviewActionStyleDefault, 18 | /** 19 | Cancel action will be put at the bottom with bold font. 20 | */ 21 | STPopupPreviewActionStyleCancel, 22 | /** 23 | Destructive action in destructive red tint color. 24 | */ 25 | STPopupPreviewActionStyleDestructive 26 | }; 27 | 28 | @interface STPopupPreviewAction : NSObject 29 | 30 | + (instancetype)actionWithTitle:(NSString *)title style:(STPopupPreviewActionStyle)style handler:(void (^)(STPopupPreviewAction *action, UIViewController *previewViewController))handler; 31 | 32 | @property (nonatomic, strong, readonly) NSString *title; 33 | @property (nonatomic, assign, readonly) STPopupPreviewActionStyle style; 34 | 35 | @end 36 | 37 | @class STPopupPreviewRecognizer; 38 | 39 | @protocol STPopupPreviewRecognizerDelegate 40 | 41 | /** 42 | A view controller for previewing. 43 | It should be configured as a popup view controller. (either "contentSizeInPopup" or "landscapeContentSizeInPopup" should be set) 44 | @see UIViewController+STPopup 45 | */ 46 | - (nullable UIViewController *)previewViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer; 47 | 48 | /** 49 | The view controller for presenting the popup. 50 | */ 51 | - (UIViewController *)presentingViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer; 52 | 53 | /** 54 | An array of STPopupPreviewAction. 55 | It could be empty if no actions are available for previewing. 56 | */ 57 | - (NSArray *)previewActionsForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer; 58 | 59 | @end 60 | 61 | typedef NS_ENUM(NSUInteger, STPopupPreviewRecognizerState) { 62 | /** 63 | Before preview view controller is presented or after preview view controller is dismissed. 64 | */ 65 | STPopupPreviewRecognizerStateNone, 66 | /** 67 | Preview view controller is presented but no preview actions are showed. 68 | */ 69 | STPopupPreviewRecognizerStatePreviewing, 70 | /** 71 | Preview actions are showed(either part of or whole of the action sheet). 72 | */ 73 | STPopupPreviewRecognizerStateShowingActions 74 | }; 75 | 76 | @interface STPopupPreviewRecognizer : NSObject 77 | 78 | /** 79 | The view the preview recognizer is attached to. 80 | */ 81 | @property (nullable, nonatomic, weak, readonly) __kindof UIView *view; 82 | 83 | /** 84 | The current state of preview recognizer. 85 | */ 86 | @property (nonatomic, assign, readonly) STPopupPreviewRecognizerState state; 87 | 88 | - (instancetype)init NS_UNAVAILABLE; 89 | - (instancetype)initWithDelegate:(id)deleagte NS_DESIGNATED_INITIALIZER; 90 | 91 | @end 92 | 93 | NS_ASSUME_NONNULL_END 94 | -------------------------------------------------------------------------------- /STPopupPreview/STPopupPreviewRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // STPopupPreviewRecognizer.m 3 | // STPopupPreview 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "STPopupPreviewRecognizer.h" 10 | #import 11 | 12 | CGFloat const STPopupPreviewActionSheetButtonHeight = 57; 13 | CGFloat const STPopupPreviewActionSheetSpacing = 10; 14 | CGFloat const STPopupPreviewShowActionsOffset = 30; 15 | 16 | @interface STPopupPreviewAction () 17 | 18 | @property (nonatomic, copy, readonly) void (^handler)(STPopupPreviewAction *, UIViewController *); 19 | 20 | - (instancetype)initWithTitle:(NSString *)title style:(STPopupPreviewActionStyle)style handler:(void (^)(STPopupPreviewAction *, UIViewController *))handler; 21 | 22 | @end 23 | 24 | @implementation STPopupPreviewAction 25 | 26 | - (instancetype)initWithTitle:(NSString *)title style:(STPopupPreviewActionStyle)style handler:(void (^)(STPopupPreviewAction *, UIViewController *))handler 27 | { 28 | if (self = [super init]) { 29 | _title = title; 30 | _style = style; 31 | _handler = [handler copy]; 32 | } 33 | return self; 34 | } 35 | 36 | + (instancetype)actionWithTitle:(NSString *)title style:(STPopupPreviewActionStyle)style handler:(void (^)(STPopupPreviewAction *, UIViewController *))handler; 37 | { 38 | return [[STPopupPreviewAction alloc] initWithTitle:title style:style handler:handler]; 39 | } 40 | 41 | @end 42 | 43 | @class STPopupPreviewActionSheet; 44 | 45 | @protocol STPopupPreviewActionSheetDelegate 46 | 47 | - (void)popupPreviewActionSheet:(STPopupPreviewActionSheet *)actionSheet didSelectAction:(STPopupPreviewAction *)action; 48 | 49 | @end 50 | 51 | /** 52 | The action sheet for internal use. 53 | */ 54 | @interface STPopupPreviewActionSheet : UIView 55 | 56 | @property (nonatomic, weak) id delegate; 57 | @property (nonatomic, strong, readonly) NSArray *actions; 58 | 59 | - (instancetype)initWithActions:(NSArray *)actions; 60 | 61 | @end 62 | 63 | @implementation STPopupPreviewActionSheet 64 | { 65 | UIView *_topContainerView; 66 | UIView *_bottomContainerView; 67 | } 68 | 69 | - (instancetype)initWithActions:(NSArray *)actions 70 | { 71 | if (self = [super init]) { 72 | _actions = actions; 73 | NSMutableArray *topActions = [NSMutableArray new]; 74 | NSMutableArray *bottomActions = [NSMutableArray new]; 75 | for (STPopupPreviewAction *action in actions) { 76 | switch (action.style) { 77 | case STPopupPreviewActionStyleDefault: 78 | case STPopupPreviewActionStyleDestructive: 79 | [topActions addObject:action]; 80 | break; 81 | case STPopupPreviewActionStyleCancel: 82 | [bottomActions addObject:action]; 83 | break; 84 | default: 85 | break; 86 | } 87 | } 88 | 89 | if (topActions.count) { 90 | _topContainerView = [self createContainerView]; 91 | [self addSubview:_topContainerView]; 92 | for (STPopupPreviewAction *action in topActions) { 93 | UIButton *button = [self createActionButtonWithAction:action showsSeparator:action != topActions.lastObject]; 94 | [_topContainerView addSubview:button]; 95 | } 96 | } 97 | if (bottomActions.count) { 98 | _bottomContainerView = [self createContainerView]; 99 | [self addSubview:_bottomContainerView]; 100 | for (STPopupPreviewAction *action in bottomActions) { 101 | UIButton *button = [self createActionButtonWithAction:action showsSeparator:action != topActions.lastObject]; 102 | [_bottomContainerView addSubview:button]; 103 | } 104 | } 105 | } 106 | return self; 107 | } 108 | 109 | - (void)layoutSubviews 110 | { 111 | [super layoutSubviews]; 112 | 113 | CGFloat spacing = STPopupPreviewActionSheetSpacing; 114 | CGFloat buttonHeight = STPopupPreviewActionSheetButtonHeight; 115 | _topContainerView.frame = CGRectMake(spacing, spacing, self.superview.bounds.size.width - spacing * 2, _topContainerView.subviews.count * buttonHeight); 116 | _bottomContainerView.frame = CGRectMake(spacing, _topContainerView.frame.origin.y + _topContainerView.frame.size.height + spacing, self.superview.bounds.size.width - spacing * 2, _bottomContainerView.subviews.count * buttonHeight); 117 | [self layoutContainerView:_topContainerView]; 118 | [self layoutContainerView:_bottomContainerView]; 119 | } 120 | 121 | - (void)layoutContainerView:(UIView *)containerView 122 | { 123 | for (UIView *subview in containerView.subviews) { 124 | subview.frame = CGRectMake(0, STPopupPreviewActionSheetButtonHeight * [containerView.subviews indexOfObject:subview], containerView.frame.size.width, STPopupPreviewActionSheetButtonHeight); 125 | } 126 | } 127 | 128 | - (void)sizeToFit 129 | { 130 | NSAssert(self.superview, @"%@ of %@ can only be called after it's added to a superview", NSStringFromSelector(_cmd), NSStringFromClass(self.class)); 131 | 132 | CGRect frame = self.frame; 133 | frame.size.width = self.superview.frame.size.width; 134 | self.frame = frame; 135 | [self layoutIfNeeded]; 136 | 137 | CGFloat safeAreaInsetsBottom = 0; 138 | if (@available(iOS 11.0, *)) { 139 | safeAreaInsetsBottom = self.superview.safeAreaInsets.bottom; 140 | } 141 | if (_bottomContainerView) { 142 | frame.size.height = _bottomContainerView.frame.origin.y + _bottomContainerView.frame.size.height + STPopupPreviewActionSheetSpacing + safeAreaInsetsBottom; 143 | } 144 | else { 145 | frame.size.height = _topContainerView.frame.origin.y + _topContainerView.frame.size.height + STPopupPreviewActionSheetSpacing + safeAreaInsetsBottom; 146 | } 147 | frame.origin = CGPointMake(0, self.superview.frame.size.height - frame.size.height); 148 | self.frame = frame; 149 | } 150 | 151 | #pragma mark - Actions 152 | 153 | - (void)actionButtonDidTap:(UIButton *)button 154 | { 155 | STPopupPreviewAction *action = self.actions[button.tag]; 156 | [self.delegate popupPreviewActionSheet:self didSelectAction:action]; 157 | } 158 | 159 | #pragma mark - Helpers 160 | 161 | - (UIView *)createContainerView 162 | { 163 | UIView *containerView = [UIView new]; 164 | containerView.layer.cornerRadius = 10; 165 | containerView.clipsToBounds = YES; 166 | containerView.backgroundColor = [UIColor colorWithRed:249/255.f green:247/255.f blue:249/255.f alpha:1]; 167 | return containerView; 168 | } 169 | 170 | - (UIButton *)createActionButtonWithAction:(STPopupPreviewAction *)action showsSeparator:(BOOL)showsSeparator 171 | { 172 | UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; 173 | button.tag = [self.actions indexOfObject:action]; 174 | [button setTitle:action.title forState:UIControlStateNormal]; 175 | [button addTarget:self action:@selector(actionButtonDidTap:) forControlEvents:UIControlEventTouchUpInside]; 176 | switch (action.style) { 177 | case STPopupPreviewActionStyleDestructive: 178 | [button setTitleColor:[UIColor colorWithRed:1 green:0.23 blue:0.19 alpha:1] forState:UIControlStateNormal]; 179 | case STPopupPreviewActionStyleDefault: 180 | button.titleLabel.font = [UIFont systemFontOfSize:20]; 181 | break; 182 | case STPopupPreviewActionStyleCancel: 183 | button.titleLabel.font = [UIFont boldSystemFontOfSize:20]; 184 | default: 185 | break; 186 | } 187 | if (showsSeparator) { 188 | UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, button.frame.size.height, button.frame.size.width, 0.5)]; 189 | separatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; 190 | separatorView.backgroundColor = [UIColor colorWithRed:179/255.f green:180/255.f blue:184/255.f alpha:1]; 191 | [button addSubview:separatorView]; 192 | } 193 | return button; 194 | } 195 | 196 | @end 197 | 198 | /** 199 | A custom view which draws the arrow indicator. 200 | */ 201 | @interface STPopupPreviewArrowView : UIView 202 | 203 | @end 204 | 205 | @implementation STPopupPreviewArrowView 206 | 207 | - (instancetype)initWithFrame:(CGRect)frame 208 | { 209 | if (self = [super initWithFrame:frame]) { 210 | self.opaque = NO; 211 | } 212 | return self; 213 | } 214 | 215 | - (void)drawRect:(CGRect)rect 216 | { 217 | [super drawRect:rect]; 218 | 219 | CGContextRef context = UIGraphicsGetCurrentContext(); 220 | 221 | CGFloat lineWidth = 5; 222 | CGFloat shadowRadius = 4; 223 | CGFloat width = rect.size.width - lineWidth - shadowRadius * 2; 224 | CGFloat height = rect.size.height - lineWidth - shadowRadius * 2; 225 | CGFloat x = (rect.size.width - width) / 2; 226 | CGFloat y = (rect.size.height - height) / 2; 227 | 228 | CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); 229 | CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 0.0f), shadowRadius, [UIColor colorWithWhite:0.2 alpha:0.2].CGColor); 230 | 231 | CGContextSetLineWidth(context, lineWidth); 232 | CGContextSetLineCap(context, kCGLineCapRound); 233 | CGContextSetLineJoin(context, kCGLineJoinBevel); 234 | 235 | CGContextMoveToPoint(context, x, y + height); 236 | CGContextAddLineToPoint(context, x + width / 2, y); 237 | CGContextAddLineToPoint(context, x + width, y + height); 238 | 239 | CGContextStrokePath(context); 240 | } 241 | 242 | @end 243 | 244 | @interface STPopupPreviewRecognizer () 245 | 246 | @property (nonatomic, weak) UIView *view; 247 | 248 | @end 249 | 250 | @implementation STPopupPreviewRecognizer 251 | { 252 | __weak id _delegate; 253 | UILongPressGestureRecognizer *_longPressGesture; 254 | UIPanGestureRecognizer *_panGesture; 255 | UITapGestureRecognizer *_tapGesture; 256 | STPopupController *_popupController; 257 | CGFloat _startPointY; 258 | STPopupPreviewArrowView *_arrowView; 259 | STPopupPreviewActionSheet *_actionSheet; 260 | } 261 | 262 | - (instancetype)initWithDelegate:(id)deleagte 263 | { 264 | if (self = [super init]) { 265 | _delegate = deleagte; 266 | } 267 | return self; 268 | } 269 | 270 | - (void)setView:(UIView *)view 271 | { 272 | if (!_longPressGesture) { 273 | _longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(gestureAction:)]; 274 | _longPressGesture.minimumPressDuration = 0.3; 275 | } 276 | [_view removeGestureRecognizer:_longPressGesture]; 277 | _view = view; 278 | [_view addGestureRecognizer:_longPressGesture]; 279 | } 280 | 281 | #pragma mark - Helpers 282 | 283 | - (void)dismissWithCompletion:(void(^)(void))completion 284 | { 285 | _state = STPopupPreviewRecognizerStateNone; 286 | [_popupController.backgroundView removeGestureRecognizer:_panGesture]; 287 | [_popupController.backgroundView removeGestureRecognizer:_tapGesture]; 288 | [_popupController dismissWithCompletion:^{ 289 | if (completion) { 290 | completion(); 291 | } 292 | [self->_arrowView removeFromSuperview]; 293 | self->_arrowView = nil; 294 | [self->_actionSheet removeFromSuperview]; 295 | self->_actionSheet = nil; 296 | self->_panGesture = nil; 297 | self->_tapGesture = nil; 298 | self->_popupController = nil; 299 | }]; 300 | } 301 | 302 | #pragma mark - Gestures 303 | 304 | - (void)gestureAction:(UIGestureRecognizer *)gesture 305 | { 306 | NSAssert(gesture == _longPressGesture || gesture == _panGesture, @"Gesture is not expected"); 307 | switch (gesture.state) { 308 | case UIGestureRecognizerStateBegan: { 309 | if (gesture == _panGesture) { // Reset _startPointY if it's from _panGesture, make sure translationY is correctly calculated 310 | _startPointY = [gesture locationInView:_popupController.backgroundView].y - _popupController.containerView.transform.ty; 311 | break; 312 | } 313 | 314 | UIViewController *previewViewController = [_delegate previewViewControllerForPopupPreviewRecognizer:self]; 315 | if (!previewViewController) { 316 | break; 317 | } 318 | 319 | _popupController = [[STPopupController alloc] initWithRootViewController:previewViewController]; 320 | _popupController.containerView.layer.cornerRadius = 10; 321 | _popupController.transitionStyle = STPopupTransitionStyleFade; 322 | _popupController.hidesCloseButton = YES; 323 | 324 | UIView *backgroundContentView = nil; 325 | if (NSClassFromString(@"UIVisualEffectView")) { 326 | UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 327 | UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 328 | _popupController.backgroundView = blurEffectView; 329 | backgroundContentView = blurEffectView.contentView; 330 | } 331 | else { // Work around for iOS 7 332 | _popupController.backgroundView = [UIToolbar new]; 333 | backgroundContentView = _popupController.backgroundView; 334 | } 335 | 336 | UIViewController *presentingViewController = [_delegate presentingViewControllerForPopupPreviewRecognizer:self]; 337 | [_popupController presentInViewController:presentingViewController completion:^{ 338 | self->_popupController.containerView.userInteractionEnabled = NO; 339 | self->_state = STPopupPreviewRecognizerStatePreviewing; 340 | self->_startPointY = [gesture locationInView:self->_popupController.backgroundView].y; 341 | 342 | NSArray *actions = [self->_delegate previewActionsForPopupPreviewRecognizer:self]; 343 | if (actions.count) { 344 | CGFloat arrowWidth = 44; 345 | CGFloat arrowHeight = 20; 346 | self->_arrowView = [[STPopupPreviewArrowView alloc] initWithFrame:CGRectMake((self->_popupController.backgroundView.frame.size.width - arrowWidth) / 2, self->_popupController.containerView.frame.origin.y - 35, arrowWidth, arrowHeight)]; 347 | [backgroundContentView addSubview:self->_arrowView]; 348 | self->_arrowView.alpha = 0; 349 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 350 | self->_arrowView.alpha = 1; 351 | } completion:nil]; 352 | 353 | self->_actionSheet = [[STPopupPreviewActionSheet alloc] initWithActions:actions]; 354 | self->_actionSheet.delegate = self; 355 | [backgroundContentView addSubview:self->_actionSheet]; 356 | [self->_actionSheet sizeToFit]; 357 | self->_actionSheet.transform = CGAffineTransformMakeTranslation(0, self->_actionSheet.frame.size.height); 358 | } 359 | 360 | self->_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureAction:)]; 361 | [self->_popupController.backgroundView addGestureRecognizer:self->_panGesture]; 362 | self->_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(containerViewDidTap)]; 363 | [self->_popupController.backgroundView addGestureRecognizer:self->_tapGesture]; 364 | }]; 365 | } 366 | break; 367 | case UIGestureRecognizerStateChanged: { 368 | if ((_state != STPopupPreviewRecognizerStatePreviewing && _state != STPopupPreviewRecognizerStateShowingActions) || 369 | !_actionSheet) { 370 | break; 371 | } 372 | 373 | CGPoint currentPoint = [gesture locationInView:_popupController.backgroundView]; 374 | CGFloat translationY = currentPoint.y - _startPointY; 375 | _popupController.containerView.transform = CGAffineTransformMakeTranslation(0, translationY); 376 | _arrowView.transform = _popupController.containerView.transform; 377 | 378 | if (-translationY >= STPopupPreviewShowActionsOffset) { // Start showing action sheet 379 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 380 | self->_arrowView.alpha = 0; 381 | } completion:nil]; 382 | 383 | CGFloat availableHeight = _popupController.backgroundView.frame.size.height - _popupController.containerView.frame.origin.y - _popupController.containerView.frame.size.height; 384 | if (_state != STPopupPreviewRecognizerStateShowingActions) { 385 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 386 | if (availableHeight >= self->_actionSheet.frame.size.height) { 387 | self->_actionSheet.transform = CGAffineTransformIdentity; 388 | } 389 | else { 390 | self->_actionSheet.transform = CGAffineTransformMakeTranslation(0, self->_actionSheet.frame.size.height - availableHeight); 391 | } 392 | } completion:nil]; 393 | } 394 | else { 395 | if (availableHeight >= _actionSheet.frame.size.height) { 396 | _actionSheet.transform = CGAffineTransformIdentity; 397 | } 398 | else { 399 | _actionSheet.transform = CGAffineTransformMakeTranslation(0, _actionSheet.frame.size.height - availableHeight); 400 | } 401 | } 402 | _state = STPopupPreviewRecognizerStateShowingActions; 403 | } 404 | else { // Dismiss action sheet 405 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 406 | self->_arrowView.alpha = 1; 407 | self->_actionSheet.transform = CGAffineTransformMakeTranslation(0, self->_actionSheet.frame.size.height); 408 | } completion:nil]; 409 | _state = STPopupPreviewRecognizerStatePreviewing; 410 | } 411 | } 412 | break; 413 | case UIGestureRecognizerStateCancelled: 414 | case UIGestureRecognizerStateFailed: 415 | case UIGestureRecognizerStateEnded: { 416 | if (_state == STPopupPreviewRecognizerStateShowingActions) { // Make sure action sheet is fully showed 417 | CGFloat availableHeight = _popupController.backgroundView.frame.size.height - _actionSheet.frame.size.height; 418 | CGFloat translationY = availableHeight - _popupController.containerView.frame.size.height - (_popupController.backgroundView.frame.size.height - _popupController.containerView.frame.size.height) / 2; 419 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 420 | if (translationY < 0) { 421 | self->_popupController.containerView.transform = CGAffineTransformMakeTranslation(0, translationY); 422 | } 423 | else { 424 | self->_popupController.containerView.transform = CGAffineTransformIdentity; 425 | } 426 | self->_arrowView.transform = self->_popupController.containerView.transform; 427 | self->_actionSheet.transform = CGAffineTransformIdentity; 428 | } completion:nil]; 429 | } 430 | else { 431 | [self dismissWithCompletion:nil]; 432 | } 433 | } 434 | break; 435 | default: 436 | break; 437 | } 438 | } 439 | 440 | - (void)containerViewDidTap 441 | { 442 | [self dismissWithCompletion:nil]; 443 | } 444 | 445 | #pragma mark - STPopupPreviewActionSheetDelegate 446 | 447 | - (void)popupPreviewActionSheet:(STPopupPreviewActionSheet *)actionSheet didSelectAction:(STPopupPreviewAction *)action 448 | { 449 | [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 450 | self->_popupController.containerView.transform = CGAffineTransformMakeTranslation(0, -self->_popupController.containerView.frame.size.height - (self->_popupController.backgroundView.frame.size.height - self->_popupController.containerView.frame.size.height) / 2); 451 | self->_actionSheet.transform = CGAffineTransformMakeTranslation(0, self->_actionSheet.frame.size.height); 452 | } completion:^(BOOL finished) { 453 | [self dismissWithCompletion:^{ 454 | if (action.handler) { 455 | action.handler(action, self->_popupController.topViewController); 456 | } 457 | }]; 458 | }]; 459 | } 460 | 461 | @end 462 | -------------------------------------------------------------------------------- /STPopupPreview/UIView+STPopupPreview.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+STPopupPreview.h 3 | // STPopupPreview 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UIView (STPopupPreview) 15 | 16 | /** 17 | The attached preview recognizer. 18 | */ 19 | @property (nullable, nonatomic, strong) STPopupPreviewRecognizer *popupPreviewRecognizer; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /STPopupPreview/UIView+STPopupPreview.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+STPopupPreview.m 3 | // STPopupPreview 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "UIView+STPopupPreview.h" 10 | #import 11 | 12 | @interface STPopupPreviewRecognizer (STPopupPreviewInternal) 13 | 14 | @property (nonatomic, weak) UIView *view; 15 | 16 | @end 17 | 18 | @implementation UIView (STPopupPreview) 19 | 20 | - (void)setPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 21 | { 22 | self.popupPreviewRecognizer.view = nil; 23 | popupPreviewRecognizer.view = self; 24 | objc_setAssociatedObject(self, @selector(popupPreviewRecognizer), popupPreviewRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 25 | } 26 | 27 | - (STPopupPreviewRecognizer *)popupPreviewRecognizer 28 | { 29 | return objc_getAssociatedObject(self, @selector(popupPreviewRecognizer)); 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 769647DA1CF16D17002D126E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 769647D91CF16D17002D126E /* main.m */; }; 11 | 769647DD1CF16D17002D126E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 769647DC1CF16D17002D126E /* AppDelegate.m */; }; 12 | 769647E01CF16D17002D126E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 769647DF1CF16D17002D126E /* ViewController.m */; }; 13 | 769647E31CF16D17002D126E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 769647E11CF16D17002D126E /* Main.storyboard */; }; 14 | 769647E51CF16D17002D126E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 769647E41CF16D17002D126E /* Assets.xcassets */; }; 15 | 769647E81CF16D17002D126E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 769647E61CF16D17002D126E /* LaunchScreen.storyboard */; }; 16 | 769647F81CF16D31002D126E /* STPopupPreview.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 769647F51CF16D1F002D126E /* STPopupPreview.framework */; }; 17 | 769647F91CF16D31002D126E /* STPopupPreview.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 769647F51CF16D1F002D126E /* STPopupPreview.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 7696480D1CF18E4D002D126E /* PreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7696480C1CF18E4D002D126E /* PreviewViewController.m */; }; 19 | 769648171CF1FDFC002D126E /* ExampleData.json in Resources */ = {isa = PBXBuildFile; fileRef = 769648161CF1FDFC002D126E /* ExampleData.json */; }; 20 | 7696481A1CF20F67002D126E /* ImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 769648191CF20F67002D126E /* ImageLoader.m */; }; 21 | 76BD8ED61CF22D0700A197DF /* STPopup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76BD8ED31CF22CF000A197DF /* STPopup.framework */; }; 22 | 76BD8ED71CF22D0700A197DF /* STPopup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76BD8ED31CF22CF000A197DF /* STPopup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 769647F41CF16D1F002D126E /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */; 29 | proxyType = 2; 30 | remoteGlobalIDString = 769647B81CF16C80002D126E; 31 | remoteInfo = STPopupPreview; 32 | }; 33 | 769647F61CF16D26002D126E /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 769647B71CF16C80002D126E; 38 | remoteInfo = STPopupPreview; 39 | }; 40 | 769647FA1CF16D31002D126E /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */; 43 | proxyType = 1; 44 | remoteGlobalIDString = 769647B71CF16C80002D126E; 45 | remoteInfo = STPopupPreview; 46 | }; 47 | 76BD8ED21CF22CF000A197DF /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */; 50 | proxyType = 2; 51 | remoteGlobalIDString = 76D822381C1DB5E400FCF988; 52 | remoteInfo = STPopup; 53 | }; 54 | 76BD8ED41CF22CFA00A197DF /* PBXContainerItemProxy */ = { 55 | isa = PBXContainerItemProxy; 56 | containerPortal = 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */; 57 | proxyType = 1; 58 | remoteGlobalIDString = 76D822371C1DB5E400FCF988; 59 | remoteInfo = STPopup; 60 | }; 61 | 76BD8ED81CF22D0700A197DF /* PBXContainerItemProxy */ = { 62 | isa = PBXContainerItemProxy; 63 | containerPortal = 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */; 64 | proxyType = 1; 65 | remoteGlobalIDString = 76D822371C1DB5E400FCF988; 66 | remoteInfo = STPopup; 67 | }; 68 | /* End PBXContainerItemProxy section */ 69 | 70 | /* Begin PBXCopyFilesBuildPhase section */ 71 | 769647FC1CF16D31002D126E /* Embed Frameworks */ = { 72 | isa = PBXCopyFilesBuildPhase; 73 | buildActionMask = 2147483647; 74 | dstPath = ""; 75 | dstSubfolderSpec = 10; 76 | files = ( 77 | 76BD8ED71CF22D0700A197DF /* STPopup.framework in Embed Frameworks */, 78 | 769647F91CF16D31002D126E /* STPopupPreview.framework in Embed Frameworks */, 79 | ); 80 | name = "Embed Frameworks"; 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXCopyFilesBuildPhase section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | 769647D51CF16D17002D126E /* STPopupPreviewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = STPopupPreviewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 769647D91CF16D17002D126E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 88 | 769647DB1CF16D17002D126E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 89 | 769647DC1CF16D17002D126E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 90 | 769647DE1CF16D17002D126E /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 91 | 769647DF1CF16D17002D126E /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 92 | 769647E21CF16D17002D126E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 93 | 769647E41CF16D17002D126E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 94 | 769647E71CF16D17002D126E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 95 | 769647E91CF16D17002D126E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = STPopupPreview.xcodeproj; path = ../STPopupPreview.xcodeproj; sourceTree = ""; }; 97 | 7696480B1CF18E4D002D126E /* PreviewViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreviewViewController.h; sourceTree = ""; }; 98 | 7696480C1CF18E4D002D126E /* PreviewViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreviewViewController.m; sourceTree = ""; }; 99 | 769648161CF1FDFC002D126E /* ExampleData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ExampleData.json; sourceTree = ""; }; 100 | 769648181CF20F67002D126E /* ImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageLoader.h; sourceTree = ""; }; 101 | 769648191CF20F67002D126E /* ImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageLoader.m; sourceTree = ""; }; 102 | 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = STPopup.xcodeproj; path = ../STPopup/STPopup.xcodeproj; sourceTree = ""; }; 103 | /* End PBXFileReference section */ 104 | 105 | /* Begin PBXFrameworksBuildPhase section */ 106 | 769647D21CF16D17002D126E /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | 76BD8ED61CF22D0700A197DF /* STPopup.framework in Frameworks */, 111 | 769647F81CF16D31002D126E /* STPopupPreview.framework in Frameworks */, 112 | ); 113 | runOnlyForDeploymentPostprocessing = 0; 114 | }; 115 | /* End PBXFrameworksBuildPhase section */ 116 | 117 | /* Begin PBXGroup section */ 118 | 769647CC1CF16D17002D126E = { 119 | isa = PBXGroup; 120 | children = ( 121 | 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */, 122 | 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */, 123 | 769647D71CF16D17002D126E /* STPopupPreviewExample */, 124 | 769647D61CF16D17002D126E /* Products */, 125 | ); 126 | sourceTree = ""; 127 | }; 128 | 769647D61CF16D17002D126E /* Products */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 769647D51CF16D17002D126E /* STPopupPreviewExample.app */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | 769647D71CF16D17002D126E /* STPopupPreviewExample */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 769647DB1CF16D17002D126E /* AppDelegate.h */, 140 | 769647DC1CF16D17002D126E /* AppDelegate.m */, 141 | 769647DE1CF16D17002D126E /* ViewController.h */, 142 | 769647DF1CF16D17002D126E /* ViewController.m */, 143 | 7696480B1CF18E4D002D126E /* PreviewViewController.h */, 144 | 7696480C1CF18E4D002D126E /* PreviewViewController.m */, 145 | 769648181CF20F67002D126E /* ImageLoader.h */, 146 | 769648191CF20F67002D126E /* ImageLoader.m */, 147 | 769648161CF1FDFC002D126E /* ExampleData.json */, 148 | 769647E11CF16D17002D126E /* Main.storyboard */, 149 | 769647E41CF16D17002D126E /* Assets.xcassets */, 150 | 769647E61CF16D17002D126E /* LaunchScreen.storyboard */, 151 | 769647E91CF16D17002D126E /* Info.plist */, 152 | 769647D81CF16D17002D126E /* Supporting Files */, 153 | ); 154 | path = STPopupPreviewExample; 155 | sourceTree = ""; 156 | }; 157 | 769647D81CF16D17002D126E /* Supporting Files */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 769647D91CF16D17002D126E /* main.m */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | 769647F01CF16D1F002D126E /* Products */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 769647F51CF16D1F002D126E /* STPopupPreview.framework */, 169 | ); 170 | name = Products; 171 | sourceTree = ""; 172 | }; 173 | 76BD8ECF1CF22CF000A197DF /* Products */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 76BD8ED31CF22CF000A197DF /* STPopup.framework */, 177 | ); 178 | name = Products; 179 | sourceTree = ""; 180 | }; 181 | /* End PBXGroup section */ 182 | 183 | /* Begin PBXNativeTarget section */ 184 | 769647D41CF16D17002D126E /* STPopupPreviewExample */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = 769647EC1CF16D17002D126E /* Build configuration list for PBXNativeTarget "STPopupPreviewExample" */; 187 | buildPhases = ( 188 | 769647D11CF16D17002D126E /* Sources */, 189 | 769647D21CF16D17002D126E /* Frameworks */, 190 | 769647D31CF16D17002D126E /* Resources */, 191 | 769647FC1CF16D31002D126E /* Embed Frameworks */, 192 | ); 193 | buildRules = ( 194 | ); 195 | dependencies = ( 196 | 76BD8ED51CF22CFA00A197DF /* PBXTargetDependency */, 197 | 769647F71CF16D26002D126E /* PBXTargetDependency */, 198 | 769647FB1CF16D31002D126E /* PBXTargetDependency */, 199 | 76BD8ED91CF22D0700A197DF /* PBXTargetDependency */, 200 | ); 201 | name = STPopupPreviewExample; 202 | productName = STPopupPreviewExample; 203 | productReference = 769647D51CF16D17002D126E /* STPopupPreviewExample.app */; 204 | productType = "com.apple.product-type.application"; 205 | }; 206 | /* End PBXNativeTarget section */ 207 | 208 | /* Begin PBXProject section */ 209 | 769647CD1CF16D17002D126E /* Project object */ = { 210 | isa = PBXProject; 211 | attributes = { 212 | LastUpgradeCheck = 1010; 213 | ORGANIZATIONNAME = Sth4Me; 214 | TargetAttributes = { 215 | 769647D41CF16D17002D126E = { 216 | CreatedOnToolsVersion = 7.3.1; 217 | }; 218 | }; 219 | }; 220 | buildConfigurationList = 769647D01CF16D17002D126E /* Build configuration list for PBXProject "STPopupPreviewExample" */; 221 | compatibilityVersion = "Xcode 3.2"; 222 | developmentRegion = English; 223 | hasScannedForEncodings = 0; 224 | knownRegions = ( 225 | en, 226 | Base, 227 | ); 228 | mainGroup = 769647CC1CF16D17002D126E; 229 | productRefGroup = 769647D61CF16D17002D126E /* Products */; 230 | projectDirPath = ""; 231 | projectReferences = ( 232 | { 233 | ProductGroup = 76BD8ECF1CF22CF000A197DF /* Products */; 234 | ProjectRef = 76BD8ECE1CF22CF000A197DF /* STPopup.xcodeproj */; 235 | }, 236 | { 237 | ProductGroup = 769647F01CF16D1F002D126E /* Products */; 238 | ProjectRef = 769647EF1CF16D1F002D126E /* STPopupPreview.xcodeproj */; 239 | }, 240 | ); 241 | projectRoot = ""; 242 | targets = ( 243 | 769647D41CF16D17002D126E /* STPopupPreviewExample */, 244 | ); 245 | }; 246 | /* End PBXProject section */ 247 | 248 | /* Begin PBXReferenceProxy section */ 249 | 769647F51CF16D1F002D126E /* STPopupPreview.framework */ = { 250 | isa = PBXReferenceProxy; 251 | fileType = wrapper.framework; 252 | path = STPopupPreview.framework; 253 | remoteRef = 769647F41CF16D1F002D126E /* PBXContainerItemProxy */; 254 | sourceTree = BUILT_PRODUCTS_DIR; 255 | }; 256 | 76BD8ED31CF22CF000A197DF /* STPopup.framework */ = { 257 | isa = PBXReferenceProxy; 258 | fileType = wrapper.framework; 259 | path = STPopup.framework; 260 | remoteRef = 76BD8ED21CF22CF000A197DF /* PBXContainerItemProxy */; 261 | sourceTree = BUILT_PRODUCTS_DIR; 262 | }; 263 | /* End PBXReferenceProxy section */ 264 | 265 | /* Begin PBXResourcesBuildPhase section */ 266 | 769647D31CF16D17002D126E /* Resources */ = { 267 | isa = PBXResourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 769648171CF1FDFC002D126E /* ExampleData.json in Resources */, 271 | 769647E81CF16D17002D126E /* LaunchScreen.storyboard in Resources */, 272 | 769647E51CF16D17002D126E /* Assets.xcassets in Resources */, 273 | 769647E31CF16D17002D126E /* Main.storyboard in Resources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXSourcesBuildPhase section */ 280 | 769647D11CF16D17002D126E /* Sources */ = { 281 | isa = PBXSourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | 769647E01CF16D17002D126E /* ViewController.m in Sources */, 285 | 7696481A1CF20F67002D126E /* ImageLoader.m in Sources */, 286 | 769647DD1CF16D17002D126E /* AppDelegate.m in Sources */, 287 | 769647DA1CF16D17002D126E /* main.m in Sources */, 288 | 7696480D1CF18E4D002D126E /* PreviewViewController.m in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXSourcesBuildPhase section */ 293 | 294 | /* Begin PBXTargetDependency section */ 295 | 769647F71CF16D26002D126E /* PBXTargetDependency */ = { 296 | isa = PBXTargetDependency; 297 | name = STPopupPreview; 298 | targetProxy = 769647F61CF16D26002D126E /* PBXContainerItemProxy */; 299 | }; 300 | 769647FB1CF16D31002D126E /* PBXTargetDependency */ = { 301 | isa = PBXTargetDependency; 302 | name = STPopupPreview; 303 | targetProxy = 769647FA1CF16D31002D126E /* PBXContainerItemProxy */; 304 | }; 305 | 76BD8ED51CF22CFA00A197DF /* PBXTargetDependency */ = { 306 | isa = PBXTargetDependency; 307 | name = STPopup; 308 | targetProxy = 76BD8ED41CF22CFA00A197DF /* PBXContainerItemProxy */; 309 | }; 310 | 76BD8ED91CF22D0700A197DF /* PBXTargetDependency */ = { 311 | isa = PBXTargetDependency; 312 | name = STPopup; 313 | targetProxy = 76BD8ED81CF22D0700A197DF /* PBXContainerItemProxy */; 314 | }; 315 | /* End PBXTargetDependency section */ 316 | 317 | /* Begin PBXVariantGroup section */ 318 | 769647E11CF16D17002D126E /* Main.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 769647E21CF16D17002D126E /* Base */, 322 | ); 323 | name = Main.storyboard; 324 | sourceTree = ""; 325 | }; 326 | 769647E61CF16D17002D126E /* LaunchScreen.storyboard */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 769647E71CF16D17002D126E /* Base */, 330 | ); 331 | name = LaunchScreen.storyboard; 332 | sourceTree = ""; 333 | }; 334 | /* End PBXVariantGroup section */ 335 | 336 | /* Begin XCBuildConfiguration section */ 337 | 769647EA1CF16D17002D126E /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_NONNULL = YES; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 347 | CLANG_WARN_BOOL_CONVERSION = YES; 348 | CLANG_WARN_COMMA = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | DEBUG_INFORMATION_FORMAT = dwarf; 368 | ENABLE_STRICT_OBJC_MSGSEND = YES; 369 | ENABLE_TESTABILITY = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu99; 371 | GCC_DYNAMIC_NO_PIC = NO; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_OPTIMIZATION_LEVEL = 0; 374 | GCC_PREPROCESSOR_DEFINITIONS = ( 375 | "DEBUG=1", 376 | "$(inherited)", 377 | ); 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 385 | MTL_ENABLE_DEBUG_INFO = YES; 386 | ONLY_ACTIVE_ARCH = YES; 387 | SDKROOT = iphoneos; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | 769647EB1CF16D17002D126E /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_ANALYZER_NONNULL = YES; 397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 398 | CLANG_CXX_LIBRARY = "libc++"; 399 | CLANG_ENABLE_MODULES = YES; 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_COMMA = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_EMPTY_BODY = YES; 408 | CLANG_WARN_ENUM_CONVERSION = YES; 409 | CLANG_WARN_INFINITE_RECURSION = YES; 410 | CLANG_WARN_INT_CONVERSION = YES; 411 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 412 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 413 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 415 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 416 | CLANG_WARN_STRICT_PROTOTYPES = YES; 417 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 423 | ENABLE_NS_ASSERTIONS = NO; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu99; 426 | GCC_NO_COMMON_BLOCKS = YES; 427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 428 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 429 | GCC_WARN_UNDECLARED_SELECTOR = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 431 | GCC_WARN_UNUSED_FUNCTION = YES; 432 | GCC_WARN_UNUSED_VARIABLE = YES; 433 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 434 | MTL_ENABLE_DEBUG_INFO = NO; 435 | SDKROOT = iphoneos; 436 | TARGETED_DEVICE_FAMILY = "1,2"; 437 | VALIDATE_PRODUCT = YES; 438 | }; 439 | name = Release; 440 | }; 441 | 769647ED1CF16D17002D126E /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | INFOPLIST_FILE = STPopupPreviewExample/Info.plist; 446 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 448 | PRODUCT_BUNDLE_IDENTIFIER = me.sth4.STPopupPreviewExample; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | }; 451 | name = Debug; 452 | }; 453 | 769647EE1CF16D17002D126E /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | INFOPLIST_FILE = STPopupPreviewExample/Info.plist; 458 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = me.sth4.STPopupPreviewExample; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | }; 463 | name = Release; 464 | }; 465 | /* End XCBuildConfiguration section */ 466 | 467 | /* Begin XCConfigurationList section */ 468 | 769647D01CF16D17002D126E /* Build configuration list for PBXProject "STPopupPreviewExample" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 769647EA1CF16D17002D126E /* Debug */, 472 | 769647EB1CF16D17002D126E /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | 769647EC1CF16D17002D126E /* Build configuration list for PBXNativeTarget "STPopupPreviewExample" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | 769647ED1CF16D17002D126E /* Debug */, 481 | 769647EE1CF16D17002D126E /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | defaultConfigurationName = Release; 485 | }; 486 | /* End XCConfigurationList section */ 487 | }; 488 | rootObject = 769647CD1CF16D17002D126E /* Project object */; 489 | } 490 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample.xcodeproj/xcuserdata/kevinlin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample.xcodeproj/xcuserdata/kevinlin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | STPopupPreviewExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 769647D41CF16D17002D126E 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/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 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/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 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/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 | 98 | 99 | 100 | 101 | 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 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 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 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/ExampleData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "node": { 4 | "dimensions": { 5 | "height": 724, 6 | "width": 1080 7 | }, 8 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/baf587f39fc898673233c821d0e438c0/5D158AE7/t51.2885-15/e35/47040604_2225202274389180_12825554975702171_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 9 | } 10 | }, 11 | { 12 | "node": { 13 | "dimensions": { 14 | "height": 724, 15 | "width": 1080 16 | }, 17 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/790c3e4fbda7245b62dee2d76589a988/5D4A02DA/t51.2885-15/e35/47585863_344575863041712_4658833601751578671_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 18 | } 19 | }, 20 | { 21 | "node": { 22 | "dimensions": { 23 | "height": 724, 24 | "width": 1080 25 | }, 26 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/1ffefb03ca7c3dc1961f4f3cddecbf23/5D2EE2F4/t51.2885-15/e35/47586234_2129638024017445_1874193309284621615_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 27 | } 28 | }, 29 | { 30 | "node": { 31 | "dimensions": { 32 | "height": 725, 33 | "width": 1080 34 | }, 35 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/accac8f10df8cddb1dd81b9cdad74937/5D2C4F28/t51.2885-15/e35/46102076_334878093770984_4220022529449337336_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 36 | } 37 | }, 38 | { 39 | "node": { 40 | "dimensions": { 41 | "height": 725, 42 | "width": 1080 43 | }, 44 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/cccb4b7d2076ffc5f11fad8f6029b848/5D154FB8/t51.2885-15/e35/44803121_439487589914681_6652480797511096856_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 45 | } 46 | }, 47 | { 48 | "node": { 49 | "dimensions": { 50 | "height": 725, 51 | "width": 1080 52 | }, 53 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/8832176961054337a730d1a9ab1492b4/5D325EC3/t51.2885-15/e35/45404530_281177489182055_8284980355976330798_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 54 | } 55 | }, 56 | { 57 | "node": { 58 | "dimensions": { 59 | "height": 725, 60 | "width": 1080 61 | }, 62 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/b2933c7d513e72ccb5012856e6adc612/5D1A1129/t51.2885-15/e35/45550731_223291021698036_1089640349949323498_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 63 | } 64 | }, 65 | { 66 | "node": { 67 | "dimensions": { 68 | "height": 725, 69 | "width": 1080 70 | }, 71 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/91eaae195798b894dbe46bc401ab7571/5D11495D/t51.2885-15/e35/43158350_909748959213839_3111941403820438433_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 72 | } 73 | }, 74 | { 75 | "node": { 76 | "dimensions": { 77 | "height": 725, 78 | "width": 1080 79 | }, 80 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/eee97fb1d0a19564f90010b4f84b0df3/5D488617/t51.2885-15/e35/42186745_1858844624204071_2508254251357242157_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 81 | } 82 | }, 83 | { 84 | "node": { 85 | "dimensions": { 86 | "height": 726, 87 | "width": 1080 88 | }, 89 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/32d4fd9318700dd2d5e7c3652521bb4c/5D33C134/t51.2885-15/e35/40083441_1905297473113143_5178162539430674432_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 90 | } 91 | }, 92 | { 93 | "node": { 94 | "dimensions": { 95 | "height": 724, 96 | "width": 1080 97 | }, 98 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/baf587f39fc898673233c821d0e438c0/5D158AE7/t51.2885-15/e35/47040604_2225202274389180_12825554975702171_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 99 | } 100 | }, 101 | { 102 | "node": { 103 | "dimensions": { 104 | "height": 724, 105 | "width": 1080 106 | }, 107 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/790c3e4fbda7245b62dee2d76589a988/5D4A02DA/t51.2885-15/e35/47585863_344575863041712_4658833601751578671_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 108 | } 109 | }, 110 | { 111 | "node": { 112 | "dimensions": { 113 | "height": 724, 114 | "width": 1080 115 | }, 116 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/1ffefb03ca7c3dc1961f4f3cddecbf23/5D2EE2F4/t51.2885-15/e35/47586234_2129638024017445_1874193309284621615_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 117 | } 118 | }, 119 | { 120 | "node": { 121 | "dimensions": { 122 | "height": 725, 123 | "width": 1080 124 | }, 125 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/accac8f10df8cddb1dd81b9cdad74937/5D2C4F28/t51.2885-15/e35/46102076_334878093770984_4220022529449337336_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 126 | } 127 | }, 128 | { 129 | "node": { 130 | "dimensions": { 131 | "height": 725, 132 | "width": 1080 133 | }, 134 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/cccb4b7d2076ffc5f11fad8f6029b848/5D154FB8/t51.2885-15/e35/44803121_439487589914681_6652480797511096856_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 135 | } 136 | }, 137 | { 138 | "node": { 139 | "dimensions": { 140 | "height": 725, 141 | "width": 1080 142 | }, 143 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/8832176961054337a730d1a9ab1492b4/5D325EC3/t51.2885-15/e35/45404530_281177489182055_8284980355976330798_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 144 | } 145 | }, 146 | { 147 | "node": { 148 | "dimensions": { 149 | "height": 725, 150 | "width": 1080 151 | }, 152 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/b2933c7d513e72ccb5012856e6adc612/5D1A1129/t51.2885-15/e35/45550731_223291021698036_1089640349949323498_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 153 | } 154 | }, 155 | { 156 | "node": { 157 | "dimensions": { 158 | "height": 725, 159 | "width": 1080 160 | }, 161 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/91eaae195798b894dbe46bc401ab7571/5D11495D/t51.2885-15/e35/43158350_909748959213839_3111941403820438433_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 162 | } 163 | }, 164 | { 165 | "node": { 166 | "dimensions": { 167 | "height": 725, 168 | "width": 1080 169 | }, 170 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/eee97fb1d0a19564f90010b4f84b0df3/5D488617/t51.2885-15/e35/42186745_1858844624204071_2508254251357242157_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 171 | } 172 | }, 173 | { 174 | "node": { 175 | "dimensions": { 176 | "height": 726, 177 | "width": 1080 178 | }, 179 | "display_url": "https://instagram.flhr2-1.fna.fbcdn.net/vp/32d4fd9318700dd2d5e7c3652521bb4c/5D33C134/t51.2885-15/e35/40083441_1905297473113143_5178162539430674432_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net", 180 | } 181 | } 182 | 183 | ] 184 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/ImageLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.h 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 23/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ImageLoader : NSObject 12 | 13 | + (UIImage *)cachedImageForURL:(NSURL *)url; 14 | + (void)loadImageForURL:(NSURL *)url completion:(void(^)(UIImage *image))completion; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/ImageLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.m 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 23/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "ImageLoader.h" 10 | 11 | static NSMutableDictionary *_cachedImages; 12 | 13 | @implementation ImageLoader 14 | 15 | + (void)load 16 | { 17 | _cachedImages = [NSMutableDictionary new]; 18 | } 19 | 20 | + (UIImage *)cachedImageForURL:(NSURL *)url 21 | { 22 | return _cachedImages[url]; 23 | } 24 | 25 | + (void)loadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))completion 26 | { 27 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 28 | NSData *imageData = [NSData dataWithContentsOfURL:url]; 29 | UIImage *image = [UIImage imageWithData:imageData]; 30 | dispatch_async(dispatch_get_main_queue(), ^{ 31 | _cachedImages[url] = image; 32 | if (completion) { 33 | completion(image); 34 | } 35 | }); 36 | }); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/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 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/PreviewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewViewController.h 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PreviewViewController : UIViewController 12 | 13 | @property (nonatomic, strong) NSDictionary *data; 14 | @property (nonatomic, strong) UIImage *placeholderImage; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/PreviewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewViewController.m 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "PreviewViewController.h" 10 | #import "ImageLoader.h" 11 | #import 12 | 13 | @interface PreviewViewController () 14 | 15 | @property (nonatomic, strong) IBOutlet UIImageView *avatarImageView; 16 | @property (nonatomic, strong) IBOutlet UIImageView *imageView; 17 | @property (nonatomic, strong) IBOutlet UILabel *captionLabel; 18 | @property (nonatomic, strong) IBOutlet NSLayoutConstraint *headerViewHeightConstraint; 19 | @property (nonatomic, strong) IBOutlet NSLayoutConstraint *footerViewHeightConstraint; 20 | 21 | @end 22 | 23 | @implementation PreviewViewController 24 | 25 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 26 | { 27 | if (self = [super initWithCoder:aDecoder]) { 28 | // Default content size 29 | self.contentSizeInPopup = CGSizeMake([UIScreen mainScreen].bounds.size.width - 20, 300); 30 | } 31 | return self; 32 | } 33 | 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 | 38 | // Resize content size based on image dimensions 39 | CGFloat imageWidth = [self.data[@"dimensions"][@"width"] doubleValue]; 40 | CGFloat imageHeight = [self.data[@"dimensions"][@"height"] doubleValue]; 41 | CGFloat contentWidth = [UIScreen mainScreen].bounds.size.width - 20; 42 | CGFloat contentHeight = contentWidth * imageHeight / imageWidth + _headerViewHeightConstraint.constant + _footerViewHeightConstraint.constant; 43 | self.contentSizeInPopup = CGSizeMake(contentWidth, contentHeight); 44 | 45 | self.avatarImageView.layer.cornerRadius = 16; 46 | NSURL *avatarImageURL = [NSURL URLWithString:@"https://instagram.flhr2-1.fna.fbcdn.net/vp/ff1bd2dbfbcef9536b84b1623b43312c/5D1BC2EA/t51.2885-19/s320x320/53241807_254795148730661_6781962409327198208_n.jpg?_nc_ht=instagram.flhr2-1.fna.fbcdn.net"]; 47 | if ([ImageLoader cachedImageForURL:avatarImageURL]) { 48 | self.avatarImageView.image = [ImageLoader cachedImageForURL:avatarImageURL]; 49 | } 50 | else { 51 | [ImageLoader loadImageForURL:avatarImageURL completion:^(UIImage *image) { 52 | self.avatarImageView.image = image; 53 | }]; 54 | } 55 | 56 | self.imageView.image = self.placeholderImage; 57 | [ImageLoader loadImageForURL:[NSURL URLWithString:self.data[@"display_url"]] completion:^(UIImage *image) { 58 | self.imageView.image = image; 59 | }]; 60 | 61 | self.captionLabel.text = @"Instagram Photo"; 62 | } 63 | 64 | - (void)viewWillAppear:(BOOL)animated 65 | { 66 | [super viewWillAppear:animated]; 67 | self.popupController.navigationBarHidden = YES; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PreviewViewController.h" 11 | #import "ImageLoader.h" 12 | #import 13 | 14 | @interface CollectionViewCell : UICollectionViewCell 15 | 16 | @property (nonatomic, strong) IBOutlet UIImageView *imageView; 17 | @property (nonatomic, strong) NSDictionary *data; 18 | 19 | @end 20 | 21 | @implementation CollectionViewCell 22 | 23 | @end 24 | 25 | @interface ViewController () 26 | 27 | @property (nonatomic, strong) IBOutlet UICollectionView *collectionView; 28 | 29 | @end 30 | 31 | @implementation ViewController 32 | { 33 | NSArray *_exampleData; 34 | } 35 | 36 | - (void)viewDidLoad 37 | { 38 | [super viewDidLoad]; 39 | 40 | NSString *path = [[NSBundle mainBundle] pathForResource:@"ExampleData" ofType:@"json"]; 41 | _exampleData = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:path] options:kNilOptions error:NULL]; 42 | } 43 | 44 | - (BOOL)isForceTouchAvailable 45 | { 46 | return [self respondsToSelector:@selector(traitCollection)] && 47 | [self.traitCollection respondsToSelector:@selector(forceTouchCapability)] && 48 | self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable; 49 | } 50 | 51 | #pragma mark - STPopupPreviewRecognizerDelegate 52 | 53 | - (UIViewController *)previewViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 54 | { 55 | if (![popupPreviewRecognizer.view isKindOfClass:[CollectionViewCell class]]) { 56 | return nil; 57 | } 58 | 59 | CollectionViewCell *cell = popupPreviewRecognizer.view; 60 | 61 | PreviewViewController *previewViewController = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([PreviewViewController class])]; 62 | previewViewController.data = cell.data; 63 | previewViewController.placeholderImage = cell.imageView.image; 64 | return previewViewController; 65 | } 66 | 67 | - (UIViewController *)presentingViewControllerForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 68 | { 69 | return self; 70 | } 71 | 72 | - (NSArray *)previewActionsForPopupPreviewRecognizer:(STPopupPreviewRecognizer *)popupPreviewRecognizer 73 | { 74 | return @[ [STPopupPreviewAction actionWithTitle:@"Like" style:STPopupPreviewActionStyleDefault handler:^(STPopupPreviewAction *action, UIViewController *previewViewController) { 75 | [[[UIAlertView alloc] initWithTitle:@"Liked" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show]; 76 | }], [STPopupPreviewAction actionWithTitle:@"Delete" style:STPopupPreviewActionStyleDestructive handler:^(STPopupPreviewAction *action, UIViewController *previewViewController) { 77 | [[[UIAlertView alloc] initWithTitle:@"Deleted" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show]; 78 | }], [STPopupPreviewAction actionWithTitle:@"Cancel" style:STPopupPreviewActionStyleCancel handler:^(STPopupPreviewAction *action, UIViewController *previewViewController) { 79 | [[[UIAlertView alloc] initWithTitle:@"Cancelled" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show]; 80 | }] ]; 81 | } 82 | 83 | #pragma mark - UICollectionViewDelegate, UICollectionViewDataSource 84 | 85 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 86 | { 87 | return _exampleData.count; 88 | } 89 | 90 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 91 | { 92 | CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([CollectionViewCell class]) forIndexPath:indexPath]; 93 | // You may want to enable popup preview recognizer only if force touch is not available by using "isForceTouchAvailable" 94 | if (!cell.popupPreviewRecognizer) { 95 | cell.popupPreviewRecognizer = [[STPopupPreviewRecognizer alloc] initWithDelegate:self]; 96 | } 97 | 98 | NSDictionary *data = _exampleData[indexPath.item][@"node"]; 99 | cell.data = data; 100 | 101 | NSURL *imageURL = [NSURL URLWithString:data[@"display_url"]]; 102 | if ([ImageLoader cachedImageForURL:imageURL]) { 103 | cell.imageView.image = [ImageLoader cachedImageForURL:imageURL]; 104 | } 105 | else { 106 | cell.imageView.image = nil; 107 | [ImageLoader loadImageForURL:imageURL completion:^(UIImage *image) { 108 | [collectionView reloadItemsAtIndexPaths:@[ indexPath ]]; 109 | }]; 110 | } 111 | return cell; 112 | } 113 | 114 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 115 | { 116 | CGFloat size = (collectionView.frame.size.width - 2) / 3; 117 | return CGSizeMake(size, size); 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /STPopupPreviewExample/STPopupPreviewExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // STPopupPreviewExample 4 | // 5 | // Created by Kevin Lin on 22/5/16. 6 | // Copyright © 2016 Sth4Me. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------