├── README.md
├── PixelPerfect
├── pp-app@3x.png
├── pp-pp@3x.png
├── pp-both@3x.png
├── icon-close@3x.png
├── PixelPerfectConfig.swift
├── PixelPerfectImageViewCell.swift
├── PixelPerfect.h
├── PixelPerfectView.swift
├── PixelPerfectSlider.swift
├── Info.plist
├── PixelPerfectCommon.swift
├── PixelPerfectController.swift
├── PixelPerfectActionButton.swift
├── ios-build-framework-script.sh
├── UIDoubleTapAndWaitGestureRecognizer.swift
├── PixelPerfectPopover.swift
├── PixelPerfectItemViewCell.xib
├── PixelPerfectActionButton.xib
├── PixelPerfectMagnifier.swift
├── PixelPerfectLayout.swift
└── PixelPerfectPopover.xib
├── Sample
├── pixelperfect.bundle
│ ├── first-screen.png
│ └── second-screen.png
├── ViewController.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── AppDelegate.swift
├── PixelPerfect.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ └── PixelPerfect.xcscheme
└── project.pbxproj
├── .gitignore
└── LICENSE
/README.md:
--------------------------------------------------------------------------------
1 | # pixel-perfect-for-ios
2 | Pixel Perfect Tool for iOS
3 |
--------------------------------------------------------------------------------
/PixelPerfect/pp-app@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/PixelPerfect/pp-app@3x.png
--------------------------------------------------------------------------------
/PixelPerfect/pp-pp@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/PixelPerfect/pp-pp@3x.png
--------------------------------------------------------------------------------
/PixelPerfect/pp-both@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/PixelPerfect/pp-both@3x.png
--------------------------------------------------------------------------------
/PixelPerfect/icon-close@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/PixelPerfect/icon-close@3x.png
--------------------------------------------------------------------------------
/Sample/pixelperfect.bundle/first-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/Sample/pixelperfect.bundle/first-screen.png
--------------------------------------------------------------------------------
/Sample/pixelperfect.bundle/second-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/handsomecode/pixel-perfect-for-ios/HEAD/Sample/pixelperfect.bundle/second-screen.png
--------------------------------------------------------------------------------
/PixelPerfect.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectConfig.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 16/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct PixelPerfectConfig {
12 |
13 | let active : Bool
14 | let imageName : String
15 | let grid : Bool
16 | let magnifierCircular : Bool
17 | }
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectImageViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectImageViewCell.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 24/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectItemViewCell: UICollectionViewCell {
12 |
13 | @IBOutlet weak var image: UIImageView!
14 | @IBOutlet weak var label: UILabel!
15 |
16 | func setup(image : UIImage?, label : String) {
17 | self.image.image = image
18 | self.label.text = label
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfect.h:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfect.h
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 05/01/16.
6 | // Copyright © 2016 Handsome. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for PixelPerfect.
12 | FOUNDATION_EXPORT double PixelPerfectVersionNumber;
13 |
14 | //! Project version string for PixelPerfect.
15 | FOUNDATION_EXPORT const unsigned char PixelPerfectVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectView.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 16/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectView : UIView {
12 |
13 | func addEqualConstraint(view : UIView, constant : CGFloat, attribute : NSLayoutAttribute, parent : UIView?) -> NSLayoutConstraint{
14 | let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: NSLayoutRelation.Equal, toItem: parent, attribute: parent == nil ? NSLayoutAttribute.NotAnAttribute : attribute, multiplier: 1, constant: constant)
15 | addConstraint(constraint)
16 | return constraint
17 | }
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | # Pods/
27 |
28 | # Carthage
29 | #
30 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
31 |
32 | Carthage/Build/
33 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectSlider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectSlider.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 05/01/16.
6 | // Copyright © 2016 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectSlider: UIView {
12 |
13 | var didValueChanged : ((CGFloat)->())?
14 |
15 | convenience init () {
16 | self.init(frame:CGRect.zero)
17 | let move = UIPanGestureRecognizer(target: self, action: "didFingerMoved:")
18 | addGestureRecognizer(move)
19 | }
20 |
21 | func didFingerMoved(gestureRecognizer:UIGestureRecognizer) {
22 | let position = gestureRecognizer.locationInView(self)
23 | if position.x > 0 && position.x < frame.width {
24 | didValueChanged?(1 - position.y / frame.height)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Sample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Sample
4 | //
5 | // Created by Anton Rozhkov on 07/01/16.
6 | // Copyright © 2016 Handsome. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import PixelPerfect
11 |
12 | class ViewController: UIViewController {
13 |
14 | override func viewDidLoad() {
15 | super.viewDidLoad()
16 | // Do any additional setup after loading the view, typically from a nib.
17 | }
18 |
19 | override func didReceiveMemoryWarning() {
20 | super.didReceiveMemoryWarning()
21 | // Dispose of any resources that can be recreated.
22 | }
23 |
24 |
25 | @IBAction func ppPressed(sender: AnyObject) {
26 | if PixelPerfectController.isShown() {
27 | PixelPerfectController.hide()
28 | } else {
29 | PixelPerfectController.show()
30 | }
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/PixelPerfect/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectCommon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectCommon.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 05/01/16.
6 | // Copyright © 2016 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectCommon {
12 |
13 | private static let kBundleName = "pixelperfect"
14 | private static let kBundleExt = "bundle"
15 |
16 | class func bundle() -> NSBundle {
17 | return NSBundle(identifier: "is.handsome.PixelPerfect")!
18 | }
19 |
20 | class func imageByName(name : String) -> UIImage? {
21 | return UIImage(named: "\(kBundleName).\(kBundleExt)/\(name)")!
22 | }
23 |
24 | class func getImagesBundlePath() -> String? {
25 | if let path = NSBundle.mainBundle().pathForResource(kBundleName, ofType: kBundleExt), let bundle = NSBundle(path: path) {
26 | return bundle.resourcePath
27 | }
28 | return nil
29 | }
30 | }
--------------------------------------------------------------------------------
/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectController.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 15/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class PixelPerfectController {
12 |
13 | private static let sharedInstance = PixelPerfectController()
14 |
15 | private static var shown = false
16 | private static var pixelPerfectLayout : PixelPerfectLayout!
17 |
18 | public static func isShown() -> Bool {
19 | if let window = UIApplication.sharedApplication().delegate!.window, let views = window?.subviews, let pixelPerfectLayout = pixelPerfectLayout {
20 | return views.contains(pixelPerfectLayout)
21 | }
22 | return false
23 | }
24 |
25 | public static func show(name : String? = nil) {
26 | if let window = UIApplication.sharedApplication().delegate!.window {
27 | if pixelPerfectLayout == nil {
28 | pixelPerfectLayout = PixelPerfectLayout(frame: window!.frame)
29 | }
30 | if let name = name {
31 | pixelPerfectLayout.setImage(name)
32 | }
33 | window!.addSubview(pixelPerfectLayout)
34 | }
35 | }
36 |
37 | public static func hide() {
38 | if let pixelPerfectView = pixelPerfectLayout {
39 | pixelPerfectView.removeFromSuperview()
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectActionButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectActionButton.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 22/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectActionButton : UIView {
12 |
13 | enum State {
14 | case PP
15 | case APP
16 | }
17 |
18 | override func awakeFromNib() {
19 | super.awakeFromNib()
20 | xLabel.hidden = true
21 | yLabel.hidden = true
22 | }
23 |
24 | @IBOutlet weak var image: UIImageView!
25 | @IBOutlet weak var xLabel: UILabel!
26 | @IBOutlet weak var yLabel: UILabel!
27 |
28 | private var fixedOffsetX = 0
29 | private var fixedOffsetY = 0
30 |
31 | override func layoutSubviews() {
32 | super.layoutSubviews()
33 |
34 | layer.cornerRadius = min(frame.width, frame.height)/2
35 | layer.borderColor = UIColor.whiteColor().CGColor
36 | layer.borderWidth = 2
37 | layer.masksToBounds = true
38 | }
39 |
40 | func setOffset(x: Int, y: Int) {
41 | image.hidden = true
42 | xLabel.hidden = false
43 | yLabel.hidden = false
44 | updateLabels(x, y: y)
45 | }
46 |
47 | func fixOffset(x: Int, y: Int) {
48 | fixedOffsetX = x
49 | fixedOffsetY = y
50 | updateLabels(x, y: y)
51 | }
52 |
53 | func setState(state : State) {
54 | if !xLabel.hidden {
55 | return
56 | }
57 | if state == .PP {
58 | image.image = UIImage(named: "pp-pp", inBundle: PixelPerfectCommon.bundle(), compatibleWithTraitCollection: nil)
59 | } else if state == .APP {
60 | image.image = UIImage(named: "pp-app", inBundle: PixelPerfectCommon.bundle(), compatibleWithTraitCollection: nil)
61 | }
62 | }
63 |
64 | private func updateLabels(x: Int, y: Int) {
65 | xLabel.text = "\(x - fixedOffsetX)px"
66 | yLabel.text = "\(y - fixedOffsetY)px"
67 | }
68 | }
--------------------------------------------------------------------------------
/PixelPerfect/ios-build-framework-script.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | set +u
3 | # Avoid recursively calling this script.
4 | if [[ $SF_MASTER_SCRIPT_RUNNING ]]
5 | then
6 | exit 0
7 | fi
8 | set -u
9 | export SF_MASTER_SCRIPT_RUNNING=1
10 |
11 |
12 | # Constants
13 | SF_TARGET_NAME=${PROJECT_NAME}
14 | UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
15 |
16 | # Take build target
17 | if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
18 | then
19 | SF_SDK_PLATFORM=${BASH_REMATCH[1]}
20 | else
21 | echo "Could not find platform name from SDK_NAME: $SDK_NAME"
22 | exit 1
23 | fi
24 |
25 | if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
26 | then
27 | echo "Please choose iPhone simulator as the build target."
28 | exit 1
29 | fi
30 |
31 | IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos
32 |
33 | # Build the other (non-simulator) platform
34 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION
35 |
36 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION
37 |
38 | # Copy the framework structure to the universal folder (clean it first)
39 | rm -rf "${UNIVERSAL_OUTPUTFOLDER}"
40 | mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
41 | cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework"
42 |
43 | # Smash them together to combine all architectures
44 | lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}"
--------------------------------------------------------------------------------
/Sample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Sample
4 | //
5 | // Created by Anton Rozhkov on 07/01/16.
6 | // Copyright © 2016 Handsome. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/PixelPerfect/UIDoubleTapAndWaitGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDoubleTapAndWaitGestureRecognizer.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 23/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UIKit.UIGestureRecognizerSubclass
11 |
12 | class UIDoubleTapAndWaitGestureRecognizer : UIGestureRecognizer {
13 |
14 | private let kDoubleTapInterval = 0.2
15 | private let kWailInterval = 0.6
16 |
17 | private var doubleTapTimer : NSTimer?
18 | private var waitTimer : NSTimer?
19 | private var isFirstTapDetected = false
20 | private var isDoubleTapDetected = false
21 |
22 | override func touchesBegan(touches: Set, withEvent event: UIEvent) {
23 | super.touchesBegan(touches, withEvent: event)
24 | if touches.count != 1 || isDoubleTapDetected {
25 | isFirstTapDetected = false
26 | state = .Failed
27 | return
28 | }
29 | if !isFirstTapDetected {
30 | isFirstTapDetected = true
31 |
32 | doubleTapTimer = NSTimer.scheduledTimerWithTimeInterval(kDoubleTapInterval, target: self, selector: "doubleTapTimeExpired", userInfo: nil, repeats: false)
33 | state = .Possible
34 | } else {
35 | isFirstTapDetected = false
36 | isDoubleTapDetected = true
37 | doubleTapTimer?.invalidate()
38 | waitTimer = NSTimer.scheduledTimerWithTimeInterval(kWailInterval, target: self, selector: "waitTimeExpired", userInfo: nil, repeats: false)
39 | state = .Began
40 | }
41 | }
42 |
43 | func doubleTapTimeExpired() {
44 | state = .Failed
45 | isFirstTapDetected = false
46 | isDoubleTapDetected = false
47 | }
48 |
49 | func waitTimeExpired() {
50 | state = .Ended
51 | isFirstTapDetected = false
52 | isDoubleTapDetected = false
53 | }
54 |
55 | override func touchesEnded(touches: Set, withEvent event: UIEvent) {
56 | super.touchesEnded(touches, withEvent: event)
57 | if isDoubleTapDetected {
58 | state = .Failed
59 | waitTimer?.invalidate()
60 | }
61 | isDoubleTapDetected = false
62 | }
63 |
64 | override func touchesMoved(touches: Set, withEvent event: UIEvent) {
65 | super.touchesMoved(touches, withEvent: event)
66 | state = .Failed
67 | waitTimer?.invalidate()
68 | doubleTapTimer?.invalidate()
69 | isFirstTapDetected = false
70 | isDoubleTapDetected = false
71 | }
72 | }
--------------------------------------------------------------------------------
/Sample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/PixelPerfect.xcodeproj/xcshareddata/xcschemes/PixelPerfect.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectPopover.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectPopover.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 16/12/15.
6 | // Copyright © 2015 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectPopover : PixelPerfectView {
12 |
13 | var didClose : ((PixelPerfectConfig) -> ())?
14 |
15 | @IBOutlet weak var activeSwitch: UISwitch!
16 | @IBOutlet weak var gridSwitch: UISwitch!
17 | @IBOutlet weak var magnifierShapeSegmentedControl: UISegmentedControl!
18 | @IBOutlet weak var imageNameLabel: UILabel!
19 | @IBOutlet weak var imagesCollectionView: UICollectionView!
20 |
21 | private var imagesNames : [String]!
22 |
23 | override func awakeFromNib() {
24 | super.awakeFromNib()
25 | layer.cornerRadius = 4
26 | self.layer.shadowColor = UIColor.blackColor().CGColor
27 | self.layer.shadowOpacity = 0.5
28 | self.layer.shadowRadius = 6.0
29 | self.layer.masksToBounds = false
30 |
31 | setupCollectionView()
32 | }
33 |
34 | func setImageNames(imagesNames : [String]) {
35 | self.imagesNames = imagesNames
36 | }
37 |
38 | func restore(config : PixelPerfectConfig?) {
39 | if let config = config {
40 | activeSwitch.on = config.active
41 | gridSwitch.on = config.grid
42 | imageNameLabel.text = config.imageName
43 | magnifierShapeSegmentedControl.selectedSegmentIndex = config.magnifierCircular ? 0 : 1
44 | }
45 | }
46 |
47 | override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
48 |
49 | }
50 |
51 | @IBAction func closePressed(sender: AnyObject) {
52 | if imagesCollectionView.hidden == false {
53 | imagesCollectionView.hidden = true
54 | return
55 | }
56 | let config = PixelPerfectConfig(active: activeSwitch.on, imageName: imageNameLabel.text!, grid: gridSwitch.on, magnifierCircular : magnifierShapeSegmentedControl.selectedSegmentIndex == 0)
57 | didClose?(config)
58 | }
59 |
60 | @IBAction func changeImagePressed(sender: AnyObject) {
61 | imagesCollectionView.hidden = false
62 | }
63 | }
64 |
65 | extension PixelPerfectPopover : UICollectionViewDataSource {
66 |
67 | func setupCollectionView() {
68 | imagesCollectionView.registerNib(UINib(nibName: "PixelPerfectItemViewCell", bundle: PixelPerfectCommon.bundle()), forCellWithReuseIdentifier: "albumItem")
69 | }
70 |
71 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
72 | return 1
73 | }
74 |
75 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
76 | return imagesNames.count
77 | }
78 |
79 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
80 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier("albumItem", forIndexPath: indexPath) as! PixelPerfectItemViewCell
81 | cell.setup(PixelPerfectCommon.imageByName(imagesNames[indexPath.row]), label: imagesNames[indexPath.row])
82 | return cell
83 | }
84 | }
85 |
86 | extension PixelPerfectPopover : UICollectionViewDelegateFlowLayout {
87 |
88 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
89 | return CGSize(width: 100, height: 100)
90 | }
91 | }
92 |
93 | extension PixelPerfectPopover : UICollectionViewDelegate {
94 |
95 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
96 | imageNameLabel.text = imagesNames[indexPath.row]
97 | imagesCollectionView.hidden = true
98 | }
99 | }
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectItemViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectActionButton.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
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 |
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectMagnifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelPerfectMagnifier.swift
3 | // PixelPerfect
4 | //
5 | // Created by Anton Rozhkov on 05/01/16.
6 | // Copyright © 2016 Farecompare. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PixelPerfectMagnifier : UIView, UIGestureRecognizerDelegate {
12 |
13 | private let kGridLinesCount : Int = 8
14 | private let kAreaSize : CGFloat = 200
15 | private let kZoom : CGFloat = 3
16 |
17 | private var showGrid : Bool?
18 | private var isCircular : Bool?
19 |
20 | private var area : CGRect?
21 | private var appImageFrame : CGRect?
22 | private var overlayImageFrame : CGRect?
23 |
24 | private var appImage : UIImage?
25 | private var overlayImage : UIImage?
26 | private var startMovingPoint : CGPoint?
27 |
28 | private var dx : CGFloat = 0
29 | private var dy : CGFloat = 0
30 | private var overlayAlpha : CGFloat = 0
31 |
32 | init (showGrid : Bool, isCircular : Bool) {
33 | super.init(frame:CGRect.zero)
34 | self.showGrid = showGrid
35 | self.isCircular = isCircular
36 | backgroundColor = UIColor(white: 0.0, alpha: 0.0)
37 | opaque = false;
38 | }
39 |
40 | override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
41 | return area!.contains(point)
42 | }
43 |
44 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
45 | return area!.contains(touch.locationInView(self))
46 | }
47 |
48 | required init?(coder aDecoder: NSCoder) {
49 | fatalError("Magnifier does not support NSCoding")
50 | }
51 |
52 | func setImages(appImage : UIImage?, overlayImage : UIImage?) {
53 | self.overlayImage = overlayImage
54 | self.appImage = appImage
55 | if let appImage = appImage {
56 | self.frame = CGRect(x: 0, y: 0, width: appImage.size.width, height: appImage.size.height)
57 | }
58 | setNeedsDisplay()
59 | }
60 |
61 | func setOverlayOpacity(overlayAlpha : CGFloat) {
62 | self.overlayAlpha = overlayAlpha
63 | setNeedsDisplay()
64 | }
65 |
66 | func setOverlayOffset(dx : CGFloat, dy : CGFloat) {
67 | self.dx = dx
68 | self.dy = dy
69 | self.overlayImageFrame = appImageFrame?.offsetBy(dx: dx * kZoom , dy: dy * kZoom)
70 | setNeedsDisplay()
71 | }
72 |
73 | func setPoint(point : CGPoint) {
74 | appImageFrame = CGRect(x: -point.x * (kZoom - 1), y: -point.y * (kZoom - 1), width: frame.width * kZoom, height: frame.height * kZoom)
75 | overlayImageFrame = appImageFrame?.offsetBy(dx: dx * kZoom , dy: dy * kZoom)
76 | var x = point.x - kAreaSize / 2
77 | x = x > 0 ? x : 0
78 | x = x > frame.width - kAreaSize ? frame.width - kAreaSize : x
79 | var y = point.y - kAreaSize / 2
80 | y = y > 0 ? y : 0
81 | y = y > frame.height - kAreaSize ? frame.height - kAreaSize : y
82 | area = CGRect(x: x, y: y, width: kAreaSize, height: kAreaSize)
83 | setNeedsDisplay()
84 | }
85 |
86 | func move(point : CGPoint, initialTouchPoint : CGPoint) {
87 | if startMovingPoint == nil {
88 | startMovingPoint = CGPoint(x: point.x - initialTouchPoint.x, y: point.y - initialTouchPoint.y)
89 | }
90 |
91 | if let area = area, imageFrame = appImageFrame {
92 | var areadx = point.x - startMovingPoint!.x
93 | var aready = point.y - startMovingPoint!.y
94 | var imagedx = -areadx * (kZoom - 1)
95 | var imagedy = -aready * (kZoom - 1)
96 |
97 | if (area.origin.x == 0) || (area.origin.x + area.width == frame.width) {
98 | imagedx *= 2
99 | }
100 |
101 | if (area.origin.y == 0) || (area.origin.y + area.height == frame.height) {
102 | imagedy *= 2
103 | }
104 |
105 | imagedx = imageFrame.origin.x + imagedx > 0 ? -imageFrame.origin.x : imagedx
106 | imagedx = imageFrame.origin.x + imagedx + imageFrame.width < frame.width ? frame.width - (imageFrame.origin.x + imageFrame.width) : imagedx
107 |
108 | imagedy = imageFrame.origin.y + imagedy > 0 ? -imageFrame.origin.y : imagedy
109 | imagedy = imageFrame.origin.y + imagedy + imageFrame.height < frame.height ? frame.height - (imageFrame.origin.y + imageFrame.height) : imagedy
110 |
111 | areadx = area.origin.x + areadx < 0 ? -area.origin.x : areadx
112 | areadx = area.origin.x + areadx + area.width > frame.width ? frame.width - (area.origin.x + area.width) : areadx
113 |
114 | areadx = imageFrame.origin.x + imagedx > -area.width && area.origin.x == 0 ? 0: areadx
115 | areadx = imageFrame.origin.x + imageFrame.width + imagedx < frame.width + area.width && area.origin.x == frame.width - area.width ? 0: areadx
116 |
117 | aready = area.origin.y + aready < 0 ? -area.origin.y : aready
118 | aready = area.origin.y + aready + area.height > frame.height ? frame.height - (area.origin.y + area.height) : aready
119 |
120 | aready = imageFrame.origin.y + imagedy > -area.height && area.origin.y == 0 ? 0: aready
121 | aready = imageFrame.origin.y + imageFrame.height + imagedy < frame.height + area.height && area.origin.y == frame.height - area.height ? 0: aready
122 |
123 | self.area?.offsetInPlace(dx: areadx, dy: aready)
124 | self.appImageFrame?.offsetInPlace(dx: imagedx, dy: imagedy)
125 | self.overlayImageFrame?.offsetInPlace(dx: imagedx, dy: imagedy)
126 |
127 | startMovingPoint = point
128 | setNeedsDisplay()
129 | }
130 | }
131 |
132 | func endMove() {
133 | startMovingPoint = nil
134 | }
135 |
136 | func isPointInside(point : CGPoint) -> Bool {
137 | return area != nil && area!.contains(CGPoint(x: point.x - frame.origin.x, y: point.y - frame.origin.y))
138 | }
139 |
140 | override func drawRect(rect: CGRect) {
141 | if let area = area, let appImageFrame = appImageFrame, let overlayImageFrame = overlayImageFrame {
142 |
143 | let circularPath = isCircular == nil || isCircular! ? UIBezierPath(ovalInRect: area) : UIBezierPath(rect: area)
144 | circularPath.addClip()
145 | appImage?.drawInRect(appImageFrame)
146 | overlayImage?.drawInRect(overlayImageFrame, blendMode: .Normal, alpha: overlayAlpha)
147 | circularPath.stroke()
148 |
149 | guard let showGrid = showGrid else {
150 | return
151 | }
152 |
153 | if showGrid {
154 | UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.3).setStroke()
155 | let linePath = UIBezierPath()
156 | let linesDelta = kAreaSize / (CGFloat(kGridLinesCount) + 1)
157 | for i in 0.. Bool {
62 | return actionButton.frame.contains(point) || popover != nil ? true : abortTouch
63 | }
64 |
65 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
66 | return popover == nil && !actionButton.frame.contains(touch.locationInView(self))
67 | }
68 |
69 | func actionPressed(gestureRecognizer:UIGestureRecognizer) {
70 | if popover != nil {
71 | return
72 | }
73 | hideMagnifierIfNeeded()
74 | popover = PixelPerfectCommon.bundle().loadNibNamed("PixelPerfectPopover", owner: self, options: nil).first as! PixelPerfectPopover
75 | popover.setImageNames(imagesNames)
76 | popover.restore(getConfigOrDefault())
77 | popover.didClose = { pixelPerfectConfig in
78 | self.config = pixelPerfectConfig
79 | self.abortTouch = pixelPerfectConfig.active
80 | self.actionButton.setState(pixelPerfectConfig.active ? .PP : .APP)
81 | self.setImage(pixelPerfectConfig.imageName)
82 | self.popover.removeFromSuperview()
83 | self.popover = nil
84 | }
85 | popover.translatesAutoresizingMaskIntoConstraints = false
86 | addSubview(popover)
87 |
88 | addEqualConstraint(popover, constant: 10, attribute: .Leading, parent: self)
89 | addEqualConstraint(popover, constant: -10, attribute: .Trailing, parent: self)
90 | addEqualConstraint(popover, constant: 20, attribute: .Top, parent: self)
91 | addEqualConstraint(popover, constant: -20, attribute: .Bottom, parent: self)
92 | }
93 |
94 | func actionDoubleTapped(gestureRecognizer:UIGestureRecognizer) {
95 | hideMagnifierIfNeeded()
96 | abortTouch = !abortTouch
97 | actionButton.setState(abortTouch ? .PP : .APP)
98 | config = PixelPerfectConfig(active : abortTouch, imageName : getConfigOrDefault().imageName, grid : getConfigOrDefault().grid, magnifierCircular : getConfigOrDefault().magnifierCircular)
99 | }
100 |
101 | func actionLongPress(gestureRecognizer:UIGestureRecognizer) {
102 | if popover != nil {
103 | return
104 | }
105 | hideMagnifierIfNeeded()
106 | actionButton.center = gestureRecognizer.locationInView(self)
107 | actionButtonTrailing.constant = actionButton.frame.origin.x + actionButton.frame.width - frame.width
108 | actionButtonBottom.constant = actionButton.frame.origin.y + actionButton.frame.height - frame.height
109 | layoutIfNeeded()
110 | }
111 |
112 | func showZoom(gestureRecognizer:UIGestureRecognizer) {
113 | if popover != nil {
114 | return
115 | }
116 | if hideMagnifierIfNeeded() {
117 | return
118 | }
119 | if gestureRecognizer.state == .Ended {
120 | magnifier = PixelPerfectMagnifier(showGrid: getConfigOrDefault().grid, isCircular: getConfigOrDefault().magnifierCircular)
121 |
122 | actionButton.hidden = true
123 | imageView.hidden = true
124 | let appImage = makeScreenshot()
125 | imageView.hidden = false
126 | magnifier!.setImages(appImage, overlayImage: imageView.image)
127 | magnifier!.setOverlayOffset(imageView.frame.origin.x, dy: imageView.frame.origin.y)
128 | magnifier!.setOverlayOpacity(imageView.alpha)
129 | actionButton.hidden = false
130 | magnifier!.setPoint(gestureRecognizer.locationInView(self))
131 |
132 | let move = UIPanGestureRecognizer(target: self, action: "moveMagnifier:")
133 | move.delegate = magnifier
134 | magnifier!.addGestureRecognizer(move)
135 |
136 | let tap = UITapGestureRecognizer(target: self, action: "tapMagnifier:")
137 | magnifier!.addGestureRecognizer(tap)
138 | addSubview(magnifier!)
139 | }
140 | }
141 |
142 | func tapMagnifier(gestureRecognizer:UIGestureRecognizer) {
143 | guard let magnifier = magnifier else {
144 | return
145 | }
146 | let finger = gestureRecognizer.locationInView(self)
147 | if !magnifier.isPointInside(finger) {
148 | magnifier.removeFromSuperview()
149 | self.magnifier = nil
150 | }
151 | }
152 |
153 | func moveMagnifier(gestureRecognizer:UIPanGestureRecognizer) {
154 | guard let magnifier = magnifier else {
155 | return
156 | }
157 | let finger = gestureRecognizer.locationInView(self)
158 | if gestureRecognizer.state == .Ended {
159 | magnifier.endMove()
160 | } else {
161 | magnifier.move(finger, initialTouchPoint: gestureRecognizer.translationInView(self))
162 | }
163 | }
164 |
165 | func resetPosition(gestureRecognizer:UIPanGestureRecognizer) {
166 | if gestureRecognizer.state == .Ended {
167 | actionButton.fixOffset(-Int(imageView.frame.origin.x * UIScreen.mainScreen().scale), y: -Int(imageView.frame.origin.y * UIScreen.mainScreen().scale))
168 | }
169 | }
170 |
171 | func moveImage(gestureRecognizer:UIGestureRecognizer) {
172 | if popover != nil {
173 | return
174 | }
175 | if gestureRecognizer.state == .Began {
176 | startDraggingPoint = gestureRecognizer.locationInView(self)
177 | microPositioningEnabled = true
178 | } else if gestureRecognizer.state == .Ended || gestureRecognizer.state == .Failed {
179 | isHorizontalDragging = nil
180 | microPositioningEnabled = true
181 | } else {
182 | guard let startDraggingPoint = startDraggingPoint else {
183 | return
184 | }
185 | let currentDraggingPoint = gestureRecognizer.locationInView(self)
186 | if isHorizontalDragging == nil {
187 | isHorizontalDragging = abs(currentDraggingPoint.x - startDraggingPoint.x) > abs(currentDraggingPoint.y - startDraggingPoint.y)
188 | }
189 | if microPositioningEnabled! {
190 | microPositioningEnabled = abs(currentDraggingPoint.x - startDraggingPoint.x) < kMicroPositioningOffset && abs(currentDraggingPoint.y - startDraggingPoint.y) < kMicroPositioningOffset
191 | }
192 | if isHorizontalDragging! {
193 | imageView.center.x += microPositioningEnabled! ? (currentDraggingPoint.x - startDraggingPoint.x) / kMicroPositioningFactor : currentDraggingPoint.x - startDraggingPoint.x
194 | } else {
195 | imageView.center.y += microPositioningEnabled! ? (currentDraggingPoint.y - startDraggingPoint.y) / kMicroPositioningFactor : currentDraggingPoint.y - startDraggingPoint.y
196 | }
197 | self.startDraggingPoint = currentDraggingPoint
198 |
199 | actionButton.setOffset(-Int(imageView.frame.origin.x * UIScreen.mainScreen().scale), y: -Int(imageView.frame.origin.y * UIScreen.mainScreen().scale))
200 |
201 | if let magnifier = magnifier {
202 | magnifier.setOverlayOffset(imageView.frame.origin.x, dy: imageView.frame.origin.y)
203 | }
204 | }
205 | }
206 |
207 | func setImage(name : String) {
208 | if name == currentImage {
209 | return
210 | }
211 | currentImage = name
212 | if let delegate = UIApplication.sharedApplication().delegate, let optionalWindow = delegate.window, let window = optionalWindow, let image = PixelPerfectCommon.imageByName(name) {
213 | let ratio = image.size.height / image.size.width
214 | let frame = CGRect(x: 0, y: 0, width: window.frame.width, height: window.frame.width * ratio)
215 | imageView.frame = frame
216 | imageView.image = image
217 | }
218 | }
219 |
220 | private func addImageView() {
221 | addSubview(imageView)
222 | imageView.alpha = 0.5
223 | imageView.userInteractionEnabled = true
224 | }
225 |
226 | private func addActionButton() {
227 | actionButton = PixelPerfectCommon.bundle().loadNibNamed("PixelPerfectActionButton", owner: self, options: nil).first as! PixelPerfectActionButton
228 |
229 | actionButton.translatesAutoresizingMaskIntoConstraints = false
230 | addSubview(actionButton)
231 |
232 | actionButtonTrailing = addEqualConstraint(actionButton, constant: -10, attribute: .Trailing, parent: self)
233 | actionButtonBottom = addEqualConstraint(actionButton, constant: -10, attribute: .Bottom, parent: self)
234 | addEqualConstraint(actionButton, constant: 60, attribute: .Width, parent: nil)
235 | addEqualConstraint(actionButton, constant: 60, attribute: .Height, parent: nil)
236 |
237 | let longPress = UILongPressGestureRecognizer(target: self, action: "actionLongPress:")
238 | actionButton.addGestureRecognizer(longPress)
239 |
240 | let tap = UITapGestureRecognizer(target: self, action: "actionPressed:")
241 | actionButton.addGestureRecognizer(tap)
242 |
243 | let doubleTap = UITapGestureRecognizer(target: self, action: "actionDoubleTapped:")
244 | doubleTap.numberOfTapsRequired = 2
245 |
246 | tap.requireGestureRecognizerToFail(doubleTap)
247 | actionButton.addGestureRecognizer(doubleTap)
248 | actionButton.setState(.PP)
249 | }
250 |
251 | private func addSlider() {
252 | opacitySlider.translatesAutoresizingMaskIntoConstraints = false
253 | opacitySlider.didValueChanged = { value in
254 | self.imageView.alpha = value
255 | if let magnifier = self.magnifier {
256 | magnifier.setOverlayOpacity(value)
257 | }
258 | }
259 | addSubview(opacitySlider)
260 |
261 | addEqualConstraint(opacitySlider, constant: 0, attribute: .Trailing, parent: self)
262 | addEqualConstraint(opacitySlider, constant: 20, attribute: .Width, parent: nil)
263 | addEqualConstraint(opacitySlider, constant: 20, attribute: .Top, parent: self)
264 | addEqualConstraint(opacitySlider, constant: -20, attribute: .Bottom, parent: self)
265 | }
266 |
267 | private func addGestureRecognizers() {
268 | let move = UIPanGestureRecognizer(target: self, action: "moveImage:")
269 | move.delegate = self
270 | addGestureRecognizer(move)
271 |
272 | let doubleTapAndWait = UIDoubleTapAndWaitGestureRecognizer(target: self, action: "resetPosition:")
273 | addGestureRecognizer(doubleTapAndWait)
274 |
275 | let tap = UITapGestureRecognizer(target: self, action: "showZoom:")
276 | tap.numberOfTapsRequired = 1
277 | tap.delegate = self
278 | tap.requireGestureRecognizerToFail(doubleTapAndWait)
279 | addGestureRecognizer(tap)
280 | }
281 |
282 | private func makeScreenshot(view : UIView? = nil) -> UIImage? {
283 | var layer : CALayer?
284 | if let view = view {
285 | layer = view.layer
286 | } else if let delegate = UIApplication.sharedApplication().delegate, let optionalWindow = delegate.window, let window = optionalWindow {
287 | layer = window.layer
288 | }
289 | if layer != nil {
290 | UIGraphicsBeginImageContextWithOptions(self.frame.size, self.opaque, 0.0)
291 | layer!.renderInContext(UIGraphicsGetCurrentContext()!)
292 | let image = UIGraphicsGetImageFromCurrentImageContext()
293 | UIGraphicsEndImageContext()
294 | return image
295 | }
296 | return nil
297 | }
298 |
299 | private func getConfigOrDefault() -> PixelPerfectConfig {
300 | if config != nil {
301 | return config!
302 | }
303 | config = PixelPerfectConfig(active : true, imageName : imagesNames.count == 0 ? "" : imagesNames[0], grid : false, magnifierCircular : false)
304 | return config!
305 | }
306 |
307 | private func hideMagnifierIfNeeded() -> Bool {
308 | guard let magnifier = magnifier else {
309 | return false
310 | }
311 | magnifier.removeFromSuperview()
312 | self.magnifier = nil
313 | return true
314 | }
315 | }
--------------------------------------------------------------------------------
/PixelPerfect/PixelPerfectPopover.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 |
104 |
118 |
119 |
120 |
121 |
127 |
133 |
142 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/PixelPerfect.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B90C9F461C3CD903000F0833 /* icon-close@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F341C3CD903000F0833 /* icon-close@3x.png */; };
11 | B90C9F471C3CD903000F0833 /* PixelPerfectActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F351C3CD903000F0833 /* PixelPerfectActionButton.swift */; };
12 | B90C9F481C3CD903000F0833 /* PixelPerfectActionButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F361C3CD903000F0833 /* PixelPerfectActionButton.xib */; };
13 | B90C9F491C3CD903000F0833 /* PixelPerfectCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F371C3CD903000F0833 /* PixelPerfectCommon.swift */; };
14 | B90C9F4A1C3CD903000F0833 /* PixelPerfectConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F381C3CD903000F0833 /* PixelPerfectConfig.swift */; };
15 | B90C9F4B1C3CD903000F0833 /* PixelPerfectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F391C3CD903000F0833 /* PixelPerfectController.swift */; };
16 | B90C9F4C1C3CD903000F0833 /* PixelPerfectImageViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F3A1C3CD903000F0833 /* PixelPerfectImageViewCell.swift */; };
17 | B90C9F4D1C3CD903000F0833 /* PixelPerfectItemViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F3B1C3CD903000F0833 /* PixelPerfectItemViewCell.xib */; };
18 | B90C9F4E1C3CD903000F0833 /* PixelPerfectLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F3C1C3CD903000F0833 /* PixelPerfectLayout.swift */; };
19 | B90C9F4F1C3CD903000F0833 /* PixelPerfectMagnifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F3D1C3CD903000F0833 /* PixelPerfectMagnifier.swift */; };
20 | B90C9F501C3CD903000F0833 /* PixelPerfectPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F3E1C3CD903000F0833 /* PixelPerfectPopover.swift */; };
21 | B90C9F511C3CD903000F0833 /* PixelPerfectPopover.xib in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F3F1C3CD903000F0833 /* PixelPerfectPopover.xib */; };
22 | B90C9F521C3CD903000F0833 /* PixelPerfectSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F401C3CD903000F0833 /* PixelPerfectSlider.swift */; };
23 | B90C9F531C3CD903000F0833 /* PixelPerfectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F411C3CD903000F0833 /* PixelPerfectView.swift */; };
24 | B90C9F541C3CD903000F0833 /* pp-app@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F421C3CD903000F0833 /* pp-app@3x.png */; };
25 | B90C9F551C3CD903000F0833 /* pp-both@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F431C3CD903000F0833 /* pp-both@3x.png */; };
26 | B90C9F561C3CD903000F0833 /* pp-pp@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F441C3CD903000F0833 /* pp-pp@3x.png */; };
27 | B90C9F571C3CD903000F0833 /* UIDoubleTapAndWaitGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F451C3CD903000F0833 /* UIDoubleTapAndWaitGestureRecognizer.swift */; };
28 | B90C9F7D1C3E1D8A000F0833 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F7C1C3E1D8A000F0833 /* AppDelegate.swift */; };
29 | B90C9F7F1C3E1D8A000F0833 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C9F7E1C3E1D8A000F0833 /* ViewController.swift */; };
30 | B90C9F821C3E1D8A000F0833 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F801C3E1D8A000F0833 /* Main.storyboard */; };
31 | B90C9F841C3E1D8A000F0833 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F831C3E1D8A000F0833 /* Assets.xcassets */; };
32 | B90C9F871C3E1D8A000F0833 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B90C9F851C3E1D8A000F0833 /* LaunchScreen.storyboard */; };
33 | B97629DD1C3BD2AB00E06593 /* PixelPerfect.h in Headers */ = {isa = PBXBuildFile; fileRef = B97629DC1C3BD2AB00E06593 /* PixelPerfect.h */; settings = {ATTRIBUTES = (Public, ); }; };
34 | B9B2612A1C3E234B00E5BD54 /* pixelperfect.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B9B261291C3E234B00E5BD54 /* pixelperfect.bundle */; };
35 | /* End PBXBuildFile section */
36 |
37 | /* Begin PBXFileReference section */
38 | B90C9F341C3CD903000F0833 /* icon-close@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-close@3x.png"; sourceTree = ""; };
39 | B90C9F351C3CD903000F0833 /* PixelPerfectActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectActionButton.swift; sourceTree = ""; };
40 | B90C9F361C3CD903000F0833 /* PixelPerfectActionButton.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PixelPerfectActionButton.xib; sourceTree = ""; };
41 | B90C9F371C3CD903000F0833 /* PixelPerfectCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectCommon.swift; sourceTree = ""; };
42 | B90C9F381C3CD903000F0833 /* PixelPerfectConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectConfig.swift; sourceTree = ""; };
43 | B90C9F391C3CD903000F0833 /* PixelPerfectController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectController.swift; sourceTree = ""; };
44 | B90C9F3A1C3CD903000F0833 /* PixelPerfectImageViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectImageViewCell.swift; sourceTree = ""; };
45 | B90C9F3B1C3CD903000F0833 /* PixelPerfectItemViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PixelPerfectItemViewCell.xib; sourceTree = ""; };
46 | B90C9F3C1C3CD903000F0833 /* PixelPerfectLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectLayout.swift; sourceTree = ""; };
47 | B90C9F3D1C3CD903000F0833 /* PixelPerfectMagnifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectMagnifier.swift; sourceTree = ""; };
48 | B90C9F3E1C3CD903000F0833 /* PixelPerfectPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectPopover.swift; sourceTree = ""; };
49 | B90C9F3F1C3CD903000F0833 /* PixelPerfectPopover.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PixelPerfectPopover.xib; sourceTree = ""; };
50 | B90C9F401C3CD903000F0833 /* PixelPerfectSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectSlider.swift; sourceTree = ""; };
51 | B90C9F411C3CD903000F0833 /* PixelPerfectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelPerfectView.swift; sourceTree = ""; };
52 | B90C9F421C3CD903000F0833 /* pp-app@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pp-app@3x.png"; sourceTree = ""; };
53 | B90C9F431C3CD903000F0833 /* pp-both@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pp-both@3x.png"; sourceTree = ""; };
54 | B90C9F441C3CD903000F0833 /* pp-pp@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pp-pp@3x.png"; sourceTree = ""; };
55 | B90C9F451C3CD903000F0833 /* UIDoubleTapAndWaitGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDoubleTapAndWaitGestureRecognizer.swift; sourceTree = ""; };
56 | B90C9F7A1C3E1D8A000F0833 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | B90C9F7C1C3E1D8A000F0833 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
58 | B90C9F7E1C3E1D8A000F0833 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
59 | B90C9F811C3E1D8A000F0833 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
60 | B90C9F831C3E1D8A000F0833 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
61 | B90C9F861C3E1D8A000F0833 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
62 | B90C9F881C3E1D8A000F0833 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
63 | B97629D91C3BD2AB00E06593 /* PixelPerfect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PixelPerfect.framework; sourceTree = BUILT_PRODUCTS_DIR; };
64 | B97629DC1C3BD2AB00E06593 /* PixelPerfect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PixelPerfect.h; sourceTree = ""; };
65 | B97629DE1C3BD2AB00E06593 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | B9B261291C3E234B00E5BD54 /* pixelperfect.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = pixelperfect.bundle; sourceTree = ""; };
67 | /* End PBXFileReference section */
68 |
69 | /* Begin PBXFrameworksBuildPhase section */
70 | B90C9F771C3E1D8A000F0833 /* Frameworks */ = {
71 | isa = PBXFrameworksBuildPhase;
72 | buildActionMask = 2147483647;
73 | files = (
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | B97629D51C3BD2AB00E06593 /* Frameworks */ = {
78 | isa = PBXFrameworksBuildPhase;
79 | buildActionMask = 2147483647;
80 | files = (
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | /* End PBXFrameworksBuildPhase section */
85 |
86 | /* Begin PBXGroup section */
87 | B90C9F7B1C3E1D8A000F0833 /* Sample */ = {
88 | isa = PBXGroup;
89 | children = (
90 | B90C9F7C1C3E1D8A000F0833 /* AppDelegate.swift */,
91 | B90C9F7E1C3E1D8A000F0833 /* ViewController.swift */,
92 | B90C9F801C3E1D8A000F0833 /* Main.storyboard */,
93 | B9B261291C3E234B00E5BD54 /* pixelperfect.bundle */,
94 | B90C9F831C3E1D8A000F0833 /* Assets.xcassets */,
95 | B90C9F851C3E1D8A000F0833 /* LaunchScreen.storyboard */,
96 | B90C9F881C3E1D8A000F0833 /* Info.plist */,
97 | );
98 | path = Sample;
99 | sourceTree = "";
100 | };
101 | B97629CF1C3BD2AB00E06593 = {
102 | isa = PBXGroup;
103 | children = (
104 | B97629DB1C3BD2AB00E06593 /* PixelPerfect */,
105 | B90C9F7B1C3E1D8A000F0833 /* Sample */,
106 | B97629DA1C3BD2AB00E06593 /* Products */,
107 | );
108 | sourceTree = "";
109 | };
110 | B97629DA1C3BD2AB00E06593 /* Products */ = {
111 | isa = PBXGroup;
112 | children = (
113 | B97629D91C3BD2AB00E06593 /* PixelPerfect.framework */,
114 | B90C9F7A1C3E1D8A000F0833 /* Sample.app */,
115 | );
116 | name = Products;
117 | sourceTree = "";
118 | };
119 | B97629DB1C3BD2AB00E06593 /* PixelPerfect */ = {
120 | isa = PBXGroup;
121 | children = (
122 | B97629DC1C3BD2AB00E06593 /* PixelPerfect.h */,
123 | B90C9F341C3CD903000F0833 /* icon-close@3x.png */,
124 | B90C9F351C3CD903000F0833 /* PixelPerfectActionButton.swift */,
125 | B90C9F361C3CD903000F0833 /* PixelPerfectActionButton.xib */,
126 | B90C9F371C3CD903000F0833 /* PixelPerfectCommon.swift */,
127 | B90C9F381C3CD903000F0833 /* PixelPerfectConfig.swift */,
128 | B90C9F391C3CD903000F0833 /* PixelPerfectController.swift */,
129 | B90C9F3A1C3CD903000F0833 /* PixelPerfectImageViewCell.swift */,
130 | B90C9F3B1C3CD903000F0833 /* PixelPerfectItemViewCell.xib */,
131 | B90C9F3C1C3CD903000F0833 /* PixelPerfectLayout.swift */,
132 | B90C9F3D1C3CD903000F0833 /* PixelPerfectMagnifier.swift */,
133 | B90C9F3E1C3CD903000F0833 /* PixelPerfectPopover.swift */,
134 | B90C9F3F1C3CD903000F0833 /* PixelPerfectPopover.xib */,
135 | B90C9F401C3CD903000F0833 /* PixelPerfectSlider.swift */,
136 | B90C9F411C3CD903000F0833 /* PixelPerfectView.swift */,
137 | B90C9F421C3CD903000F0833 /* pp-app@3x.png */,
138 | B90C9F431C3CD903000F0833 /* pp-both@3x.png */,
139 | B90C9F441C3CD903000F0833 /* pp-pp@3x.png */,
140 | B90C9F451C3CD903000F0833 /* UIDoubleTapAndWaitGestureRecognizer.swift */,
141 | B97629DE1C3BD2AB00E06593 /* Info.plist */,
142 | );
143 | path = PixelPerfect;
144 | sourceTree = "";
145 | };
146 | /* End PBXGroup section */
147 |
148 | /* Begin PBXHeadersBuildPhase section */
149 | B97629D61C3BD2AB00E06593 /* Headers */ = {
150 | isa = PBXHeadersBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | B97629DD1C3BD2AB00E06593 /* PixelPerfect.h in Headers */,
154 | );
155 | runOnlyForDeploymentPostprocessing = 0;
156 | };
157 | /* End PBXHeadersBuildPhase section */
158 |
159 | /* Begin PBXNativeTarget section */
160 | B90C9F791C3E1D8A000F0833 /* Sample */ = {
161 | isa = PBXNativeTarget;
162 | buildConfigurationList = B90C9F891C3E1D8A000F0833 /* Build configuration list for PBXNativeTarget "Sample" */;
163 | buildPhases = (
164 | B90C9F761C3E1D8A000F0833 /* Sources */,
165 | B90C9F771C3E1D8A000F0833 /* Frameworks */,
166 | B90C9F781C3E1D8A000F0833 /* Resources */,
167 | );
168 | buildRules = (
169 | );
170 | dependencies = (
171 | );
172 | name = Sample;
173 | productName = Sample;
174 | productReference = B90C9F7A1C3E1D8A000F0833 /* Sample.app */;
175 | productType = "com.apple.product-type.application";
176 | };
177 | B97629D81C3BD2AB00E06593 /* PixelPerfect */ = {
178 | isa = PBXNativeTarget;
179 | buildConfigurationList = B97629E11C3BD2AB00E06593 /* Build configuration list for PBXNativeTarget "PixelPerfect" */;
180 | buildPhases = (
181 | B97629D41C3BD2AB00E06593 /* Sources */,
182 | B97629D51C3BD2AB00E06593 /* Frameworks */,
183 | B97629D61C3BD2AB00E06593 /* Headers */,
184 | B97629D71C3BD2AB00E06593 /* Resources */,
185 | );
186 | buildRules = (
187 | );
188 | dependencies = (
189 | );
190 | name = PixelPerfect;
191 | productName = PixelPerfect;
192 | productReference = B97629D91C3BD2AB00E06593 /* PixelPerfect.framework */;
193 | productType = "com.apple.product-type.framework";
194 | };
195 | /* End PBXNativeTarget section */
196 |
197 | /* Begin PBXProject section */
198 | B97629D01C3BD2AB00E06593 /* Project object */ = {
199 | isa = PBXProject;
200 | attributes = {
201 | LastSwiftUpdateCheck = 0720;
202 | LastUpgradeCheck = 0720;
203 | ORGANIZATIONNAME = Handsome;
204 | TargetAttributes = {
205 | B90C9F791C3E1D8A000F0833 = {
206 | CreatedOnToolsVersion = 7.2;
207 | DevelopmentTeam = YT2JVYU83V;
208 | };
209 | B97629D81C3BD2AB00E06593 = {
210 | CreatedOnToolsVersion = 7.2;
211 | DevelopmentTeam = YT2JVYU83V;
212 | };
213 | };
214 | };
215 | buildConfigurationList = B97629D31C3BD2AB00E06593 /* Build configuration list for PBXProject "PixelPerfect" */;
216 | compatibilityVersion = "Xcode 3.2";
217 | developmentRegion = English;
218 | hasScannedForEncodings = 0;
219 | knownRegions = (
220 | en,
221 | Base,
222 | );
223 | mainGroup = B97629CF1C3BD2AB00E06593;
224 | productRefGroup = B97629DA1C3BD2AB00E06593 /* Products */;
225 | projectDirPath = "";
226 | projectRoot = "";
227 | targets = (
228 | B97629D81C3BD2AB00E06593 /* PixelPerfect */,
229 | B90C9F791C3E1D8A000F0833 /* Sample */,
230 | );
231 | };
232 | /* End PBXProject section */
233 |
234 | /* Begin PBXResourcesBuildPhase section */
235 | B90C9F781C3E1D8A000F0833 /* Resources */ = {
236 | isa = PBXResourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | B90C9F871C3E1D8A000F0833 /* LaunchScreen.storyboard in Resources */,
240 | B90C9F841C3E1D8A000F0833 /* Assets.xcassets in Resources */,
241 | B9B2612A1C3E234B00E5BD54 /* pixelperfect.bundle in Resources */,
242 | B90C9F821C3E1D8A000F0833 /* Main.storyboard in Resources */,
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | B97629D71C3BD2AB00E06593 /* Resources */ = {
247 | isa = PBXResourcesBuildPhase;
248 | buildActionMask = 2147483647;
249 | files = (
250 | B90C9F551C3CD903000F0833 /* pp-both@3x.png in Resources */,
251 | B90C9F481C3CD903000F0833 /* PixelPerfectActionButton.xib in Resources */,
252 | B90C9F4D1C3CD903000F0833 /* PixelPerfectItemViewCell.xib in Resources */,
253 | B90C9F511C3CD903000F0833 /* PixelPerfectPopover.xib in Resources */,
254 | B90C9F541C3CD903000F0833 /* pp-app@3x.png in Resources */,
255 | B90C9F561C3CD903000F0833 /* pp-pp@3x.png in Resources */,
256 | B90C9F461C3CD903000F0833 /* icon-close@3x.png in Resources */,
257 | );
258 | runOnlyForDeploymentPostprocessing = 0;
259 | };
260 | /* End PBXResourcesBuildPhase section */
261 |
262 | /* Begin PBXSourcesBuildPhase section */
263 | B90C9F761C3E1D8A000F0833 /* Sources */ = {
264 | isa = PBXSourcesBuildPhase;
265 | buildActionMask = 2147483647;
266 | files = (
267 | B90C9F7F1C3E1D8A000F0833 /* ViewController.swift in Sources */,
268 | B90C9F7D1C3E1D8A000F0833 /* AppDelegate.swift in Sources */,
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | };
272 | B97629D41C3BD2AB00E06593 /* Sources */ = {
273 | isa = PBXSourcesBuildPhase;
274 | buildActionMask = 2147483647;
275 | files = (
276 | B90C9F4C1C3CD903000F0833 /* PixelPerfectImageViewCell.swift in Sources */,
277 | B90C9F531C3CD903000F0833 /* PixelPerfectView.swift in Sources */,
278 | B90C9F471C3CD903000F0833 /* PixelPerfectActionButton.swift in Sources */,
279 | B90C9F501C3CD903000F0833 /* PixelPerfectPopover.swift in Sources */,
280 | B90C9F571C3CD903000F0833 /* UIDoubleTapAndWaitGestureRecognizer.swift in Sources */,
281 | B90C9F4E1C3CD903000F0833 /* PixelPerfectLayout.swift in Sources */,
282 | B90C9F4A1C3CD903000F0833 /* PixelPerfectConfig.swift in Sources */,
283 | B90C9F4B1C3CD903000F0833 /* PixelPerfectController.swift in Sources */,
284 | B90C9F4F1C3CD903000F0833 /* PixelPerfectMagnifier.swift in Sources */,
285 | B90C9F521C3CD903000F0833 /* PixelPerfectSlider.swift in Sources */,
286 | B90C9F491C3CD903000F0833 /* PixelPerfectCommon.swift in Sources */,
287 | );
288 | runOnlyForDeploymentPostprocessing = 0;
289 | };
290 | /* End PBXSourcesBuildPhase section */
291 |
292 | /* Begin PBXVariantGroup section */
293 | B90C9F801C3E1D8A000F0833 /* Main.storyboard */ = {
294 | isa = PBXVariantGroup;
295 | children = (
296 | B90C9F811C3E1D8A000F0833 /* Base */,
297 | );
298 | name = Main.storyboard;
299 | sourceTree = "";
300 | };
301 | B90C9F851C3E1D8A000F0833 /* LaunchScreen.storyboard */ = {
302 | isa = PBXVariantGroup;
303 | children = (
304 | B90C9F861C3E1D8A000F0833 /* Base */,
305 | );
306 | name = LaunchScreen.storyboard;
307 | sourceTree = "";
308 | };
309 | /* End PBXVariantGroup section */
310 |
311 | /* Begin XCBuildConfiguration section */
312 | B90C9F8A1C3E1D8A000F0833 /* Debug */ = {
313 | isa = XCBuildConfiguration;
314 | buildSettings = {
315 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
316 | INFOPLIST_FILE = Sample/Info.plist;
317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
318 | PRODUCT_BUNDLE_IDENTIFIER = is.handsome.Sample;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | };
321 | name = Debug;
322 | };
323 | B90C9F8B1C3E1D8A000F0833 /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
327 | INFOPLIST_FILE = Sample/Info.plist;
328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
329 | PRODUCT_BUNDLE_IDENTIFIER = is.handsome.Sample;
330 | PRODUCT_NAME = "$(TARGET_NAME)";
331 | };
332 | name = Release;
333 | };
334 | B97629DF1C3BD2AB00E06593 /* Debug */ = {
335 | isa = XCBuildConfiguration;
336 | buildSettings = {
337 | ALWAYS_SEARCH_USER_PATHS = NO;
338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
339 | CLANG_CXX_LIBRARY = "libc++";
340 | CLANG_ENABLE_MODULES = YES;
341 | CLANG_ENABLE_OBJC_ARC = YES;
342 | CLANG_WARN_BOOL_CONVERSION = YES;
343 | CLANG_WARN_CONSTANT_CONVERSION = YES;
344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
345 | CLANG_WARN_EMPTY_BODY = YES;
346 | CLANG_WARN_ENUM_CONVERSION = YES;
347 | CLANG_WARN_INT_CONVERSION = YES;
348 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
349 | CLANG_WARN_UNREACHABLE_CODE = YES;
350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
351 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
352 | COPY_PHASE_STRIP = NO;
353 | CURRENT_PROJECT_VERSION = 1;
354 | DEBUG_INFORMATION_FORMAT = dwarf;
355 | ENABLE_STRICT_OBJC_MSGSEND = YES;
356 | ENABLE_TESTABILITY = YES;
357 | GCC_C_LANGUAGE_STANDARD = gnu99;
358 | GCC_DYNAMIC_NO_PIC = NO;
359 | GCC_NO_COMMON_BLOCKS = YES;
360 | GCC_OPTIMIZATION_LEVEL = 0;
361 | GCC_PREPROCESSOR_DEFINITIONS = (
362 | "DEBUG=1",
363 | "$(inherited)",
364 | );
365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
367 | GCC_WARN_UNDECLARED_SELECTOR = YES;
368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
369 | GCC_WARN_UNUSED_FUNCTION = YES;
370 | GCC_WARN_UNUSED_VARIABLE = YES;
371 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
372 | MTL_ENABLE_DEBUG_INFO = YES;
373 | ONLY_ACTIVE_ARCH = YES;
374 | SDKROOT = iphoneos;
375 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
376 | TARGETED_DEVICE_FAMILY = "1,2";
377 | VERSIONING_SYSTEM = "apple-generic";
378 | VERSION_INFO_PREFIX = "";
379 | };
380 | name = Debug;
381 | };
382 | B97629E01C3BD2AB00E06593 /* Release */ = {
383 | isa = XCBuildConfiguration;
384 | buildSettings = {
385 | ALWAYS_SEARCH_USER_PATHS = NO;
386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
387 | CLANG_CXX_LIBRARY = "libc++";
388 | CLANG_ENABLE_MODULES = YES;
389 | CLANG_ENABLE_OBJC_ARC = YES;
390 | CLANG_WARN_BOOL_CONVERSION = YES;
391 | CLANG_WARN_CONSTANT_CONVERSION = YES;
392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
393 | CLANG_WARN_EMPTY_BODY = YES;
394 | CLANG_WARN_ENUM_CONVERSION = YES;
395 | CLANG_WARN_INT_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_UNREACHABLE_CODE = YES;
398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
399 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
400 | COPY_PHASE_STRIP = NO;
401 | CURRENT_PROJECT_VERSION = 1;
402 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
403 | ENABLE_NS_ASSERTIONS = NO;
404 | ENABLE_STRICT_OBJC_MSGSEND = YES;
405 | GCC_C_LANGUAGE_STANDARD = gnu99;
406 | GCC_NO_COMMON_BLOCKS = YES;
407 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
408 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
409 | GCC_WARN_UNDECLARED_SELECTOR = YES;
410 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
411 | GCC_WARN_UNUSED_FUNCTION = YES;
412 | GCC_WARN_UNUSED_VARIABLE = YES;
413 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
414 | MTL_ENABLE_DEBUG_INFO = NO;
415 | SDKROOT = iphoneos;
416 | TARGETED_DEVICE_FAMILY = "1,2";
417 | VALIDATE_PRODUCT = YES;
418 | VERSIONING_SYSTEM = "apple-generic";
419 | VERSION_INFO_PREFIX = "";
420 | };
421 | name = Release;
422 | };
423 | B97629E21C3BD2AB00E06593 /* Debug */ = {
424 | isa = XCBuildConfiguration;
425 | buildSettings = {
426 | CLANG_ENABLE_MODULES = YES;
427 | DEFINES_MODULE = YES;
428 | DYLIB_COMPATIBILITY_VERSION = 1;
429 | DYLIB_CURRENT_VERSION = 1;
430 | DYLIB_INSTALL_NAME_BASE = "@rpath";
431 | INFOPLIST_FILE = PixelPerfect/Info.plist;
432 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
433 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
435 | PRODUCT_BUNDLE_IDENTIFIER = is.handsome.PixelPerfect;
436 | PRODUCT_NAME = "$(TARGET_NAME)";
437 | SKIP_INSTALL = YES;
438 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
439 | };
440 | name = Debug;
441 | };
442 | B97629E31C3BD2AB00E06593 /* Release */ = {
443 | isa = XCBuildConfiguration;
444 | buildSettings = {
445 | CLANG_ENABLE_MODULES = YES;
446 | DEFINES_MODULE = YES;
447 | DYLIB_COMPATIBILITY_VERSION = 1;
448 | DYLIB_CURRENT_VERSION = 1;
449 | DYLIB_INSTALL_NAME_BASE = "@rpath";
450 | INFOPLIST_FILE = PixelPerfect/Info.plist;
451 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
452 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
454 | PRODUCT_BUNDLE_IDENTIFIER = is.handsome.PixelPerfect;
455 | PRODUCT_NAME = "$(TARGET_NAME)";
456 | SKIP_INSTALL = YES;
457 | };
458 | name = Release;
459 | };
460 | /* End XCBuildConfiguration section */
461 |
462 | /* Begin XCConfigurationList section */
463 | B90C9F891C3E1D8A000F0833 /* Build configuration list for PBXNativeTarget "Sample" */ = {
464 | isa = XCConfigurationList;
465 | buildConfigurations = (
466 | B90C9F8A1C3E1D8A000F0833 /* Debug */,
467 | B90C9F8B1C3E1D8A000F0833 /* Release */,
468 | );
469 | defaultConfigurationIsVisible = 0;
470 | defaultConfigurationName = Release;
471 | };
472 | B97629D31C3BD2AB00E06593 /* Build configuration list for PBXProject "PixelPerfect" */ = {
473 | isa = XCConfigurationList;
474 | buildConfigurations = (
475 | B97629DF1C3BD2AB00E06593 /* Debug */,
476 | B97629E01C3BD2AB00E06593 /* Release */,
477 | );
478 | defaultConfigurationIsVisible = 0;
479 | defaultConfigurationName = Release;
480 | };
481 | B97629E11C3BD2AB00E06593 /* Build configuration list for PBXNativeTarget "PixelPerfect" */ = {
482 | isa = XCConfigurationList;
483 | buildConfigurations = (
484 | B97629E21C3BD2AB00E06593 /* Debug */,
485 | B97629E31C3BD2AB00E06593 /* Release */,
486 | );
487 | defaultConfigurationIsVisible = 0;
488 | defaultConfigurationName = Release;
489 | };
490 | /* End XCConfigurationList section */
491 | };
492 | rootObject = B97629D01C3BD2AB00E06593 /* Project object */;
493 | }
494 |
--------------------------------------------------------------------------------