Everything

This commit is contained in:
Shadowfacts 2020-10-28 17:14:13 -04:00
parent 4760fed8c9
commit 1a1cc4f33a
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
21 changed files with 1227 additions and 64 deletions

78
.gitignore vendored Normal file
View File

@ -0,0 +1,78 @@
.DS_Store
MyPlayground.playground/
### Swift ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
.build/
# CocoaPods - Refactored to standalone file
# Carthage - Refactored to standalone file
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
## Various settings
## Other
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno

View File

@ -7,21 +7,49 @@
objects = {
/* Begin PBXBuildFile section */
D61299E122F8FC7900BC29AD /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61299E022F8FC7900BC29AD /* Photos.framework */; };
D63DF79C23188D9000688258 /* RankingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63DF79A23188D9000688258 /* RankingTableViewCell.swift */; };
D63DF79D23188D9000688258 /* RankingTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63DF79B23188D9000688258 /* RankingTableViewCell.xib */; };
D66658CB22F655750053041D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66658CA22F655750053041D /* AppDelegate.swift */; };
D66658CD22F655750053041D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66658CC22F655750053041D /* ViewController.swift */; };
D66658D022F655750053041D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66658CE22F655750053041D /* Main.storyboard */; };
D66658D222F655760053041D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D66658D122F655760053041D /* Assets.xcassets */; };
D66658D522F655760053041D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66658D322F655760053041D /* LaunchScreen.storyboard */; };
D69E1CD422F6561B00FDB494 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69E1CD222F6561B00FDB494 /* RootViewController.swift */; };
D69E1CD522F6561B00FDB494 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D69E1CD322F6561B00FDB494 /* RootViewController.xib */; };
D69E1CDB22F656D700FDB494 /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D69E1CD822F656D700FDB494 /* AssetCollectionViewCell.xib */; };
D69E1CDC22F656D700FDB494 /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69E1CD922F656D700FDB494 /* AssetCollectionViewCell.swift */; };
D6AD654722F65DF9000148A3 /* AssetPickerViewControler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AD654622F65DF9000148A3 /* AssetPickerViewControler.swift */; };
D6AD654B22F680A2000148A3 /* AssetComparisonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AD654922F680A2000148A3 /* AssetComparisonViewController.swift */; };
D6AD654C22F680A2000148A3 /* AssetComparisonViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6AD654A22F680A2000148A3 /* AssetComparisonViewController.xib */; };
D6AD654F22F681B0000148A3 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AD654D22F681B0000148A3 /* LargeImageViewController.swift */; };
D6AD655022F681B0000148A3 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6AD654E22F681B0000148A3 /* LargeImageViewController.xib */; };
D6AD655222F68629000148A3 /* PhotosHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AD655122F68629000148A3 /* PhotosHelper.swift */; };
D6E5189622F8F44C00B4CCA1 /* RankingsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E5189422F8F44C00B4CCA1 /* RankingsTableViewController.xib */; };
D6E5189722F8F44C00B4CCA1 /* RankingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E5189522F8F44C00B4CCA1 /* RankingsTableViewController.swift */; };
D6EBEF5F22F68F19008EA503 /* Array+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBEF5E22F68F19008EA503 /* Array+Helpers.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
D61299E022F8FC7900BC29AD /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
D63DF79A23188D9000688258 /* RankingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingTableViewCell.swift; sourceTree = "<group>"; };
D63DF79B23188D9000688258 /* RankingTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RankingTableViewCell.xib; sourceTree = "<group>"; };
D66658C722F655750053041D /* PhotoRank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoRank.app; sourceTree = BUILT_PRODUCTS_DIR; };
D66658CA22F655750053041D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D66658CC22F655750053041D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
D66658CF22F655750053041D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D66658D122F655760053041D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D66658D422F655760053041D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
D66658D622F655760053041D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D69E1CD222F6561B00FDB494 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = ../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/Root/RootViewController.swift; sourceTree = "<group>"; };
D69E1CD322F6561B00FDB494 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = RootViewController.xib; path = ../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/Root/RootViewController.xib; sourceTree = "<group>"; };
D69E1CD822F656D700FDB494 /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
D69E1CD922F656D700FDB494 /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
D6AD654622F65DF9000148A3 /* AssetPickerViewControler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetPickerViewControler.swift; sourceTree = "<group>"; };
D6AD654922F680A2000148A3 /* AssetComparisonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AssetComparisonViewController.swift; path = "../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/Asset Comparison/AssetComparisonViewController.swift"; sourceTree = "<group>"; };
D6AD654A22F680A2000148A3 /* AssetComparisonViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = AssetComparisonViewController.xib; path = "../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/Asset Comparison/AssetComparisonViewController.xib"; sourceTree = "<group>"; };
D6AD654D22F681B0000148A3 /* LargeImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = "<group>"; };
D6AD654E22F681B0000148A3 /* LargeImageViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
D6AD655122F68629000148A3 /* PhotosHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PhotosHelper.swift; path = ../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/PhotosHelper.swift; sourceTree = "<group>"; };
D6E5189422F8F44C00B4CCA1 /* RankingsTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RankingsTableViewController.xib; sourceTree = "<group>"; };
D6E5189522F8F44C00B4CCA1 /* RankingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RankingsTableViewController.swift; sourceTree = "<group>"; };
D6EBEF5E22F68F19008EA503 /* Array+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Array+Helpers.swift"; path = "../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/PhotoRank/PhotoRank/Array+Helpers.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -29,17 +57,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D61299E122F8FC7900BC29AD /* Photos.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D61299DF22F8FC7900BC29AD /* Frameworks */ = {
isa = PBXGroup;
children = (
D61299E022F8FC7900BC29AD /* Photos.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
D66658BE22F655750053041D = {
isa = PBXGroup;
children = (
D66658C922F655750053041D /* PhotoRank */,
D66658C822F655750053041D /* Products */,
D61299DF22F8FC7900BC29AD /* Frameworks */,
);
sourceTree = "<group>";
};
@ -55,8 +93,12 @@
isa = PBXGroup;
children = (
D66658CA22F655750053041D /* AppDelegate.swift */,
D66658CC22F655750053041D /* ViewController.swift */,
D66658CE22F655750053041D /* Main.storyboard */,
D6AD655122F68629000148A3 /* PhotosHelper.swift */,
D6EBEF5E22F68F19008EA503 /* Array+Helpers.swift */,
D69E1CD122F6560C00FDB494 /* Root */,
D69E1CD622F656C800FDB494 /* Asset Picker */,
D6AD654822F65E18000148A3 /* Asset Comparison */,
D6E5189322F8F44C00B4CCA1 /* Final Rankings */,
D66658D122F655760053041D /* Assets.xcassets */,
D66658D322F655760053041D /* LaunchScreen.storyboard */,
D66658D622F655760053041D /* Info.plist */,
@ -64,6 +106,47 @@
path = PhotoRank;
sourceTree = "<group>";
};
D69E1CD122F6560C00FDB494 /* Root */ = {
isa = PBXGroup;
children = (
D69E1CD222F6561B00FDB494 /* RootViewController.swift */,
D69E1CD322F6561B00FDB494 /* RootViewController.xib */,
);
path = Root;
sourceTree = "<group>";
};
D69E1CD622F656C800FDB494 /* Asset Picker */ = {
isa = PBXGroup;
children = (
D6AD654622F65DF9000148A3 /* AssetPickerViewControler.swift */,
D69E1CD922F656D700FDB494 /* AssetCollectionViewCell.swift */,
D69E1CD822F656D700FDB494 /* AssetCollectionViewCell.xib */,
);
path = "Asset Picker";
sourceTree = "<group>";
};
D6AD654822F65E18000148A3 /* Asset Comparison */ = {
isa = PBXGroup;
children = (
D6AD654922F680A2000148A3 /* AssetComparisonViewController.swift */,
D6AD654A22F680A2000148A3 /* AssetComparisonViewController.xib */,
D6AD654D22F681B0000148A3 /* LargeImageViewController.swift */,
D6AD654E22F681B0000148A3 /* LargeImageViewController.xib */,
);
path = "Asset Comparison";
sourceTree = "<group>";
};
D6E5189322F8F44C00B4CCA1 /* Final Rankings */ = {
isa = PBXGroup;
children = (
D6E5189522F8F44C00B4CCA1 /* RankingsTableViewController.swift */,
D6E5189422F8F44C00B4CCA1 /* RankingsTableViewController.xib */,
D63DF79A23188D9000688258 /* RankingTableViewCell.swift */,
D63DF79B23188D9000688258 /* RankingTableViewCell.xib */,
);
path = "Final Rankings";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -123,8 +206,13 @@
buildActionMask = 2147483647;
files = (
D66658D522F655760053041D /* LaunchScreen.storyboard in Resources */,
D6E5189622F8F44C00B4CCA1 /* RankingsTableViewController.xib in Resources */,
D66658D222F655760053041D /* Assets.xcassets in Resources */,
D66658D022F655750053041D /* Main.storyboard in Resources */,
D6AD654C22F680A2000148A3 /* AssetComparisonViewController.xib in Resources */,
D69E1CD522F6561B00FDB494 /* RootViewController.xib in Resources */,
D6AD655022F681B0000148A3 /* LargeImageViewController.xib in Resources */,
D63DF79D23188D9000688258 /* RankingTableViewCell.xib in Resources */,
D69E1CDB22F656D700FDB494 /* AssetCollectionViewCell.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -135,22 +223,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D66658CD22F655750053041D /* ViewController.swift in Sources */,
D6AD654B22F680A2000148A3 /* AssetComparisonViewController.swift in Sources */,
D66658CB22F655750053041D /* AppDelegate.swift in Sources */,
D6E5189722F8F44C00B4CCA1 /* RankingsTableViewController.swift in Sources */,
D6AD654F22F681B0000148A3 /* LargeImageViewController.swift in Sources */,
D63DF79C23188D9000688258 /* RankingTableViewCell.swift in Sources */,
D6EBEF5F22F68F19008EA503 /* Array+Helpers.swift in Sources */,
D69E1CD422F6561B00FDB494 /* RootViewController.swift in Sources */,
D6AD655222F68629000148A3 /* PhotosHelper.swift in Sources */,
D6AD654722F65DF9000148A3 /* AssetPickerViewControler.swift in Sources */,
D69E1CDC22F656D700FDB494 /* AssetCollectionViewCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
D66658CE22F655750053041D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
D66658CF22F655750053041D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
D66658D322F655760053041D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
@ -285,6 +373,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = PhotoRank/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -303,6 +392,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = PhotoRank/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@ -15,7 +15,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window!.rootViewController = UINavigationController(rootViewController: RootViewController())
window!.makeKeyAndVisible()
return true
}

View File

@ -0,0 +1,17 @@
//
// Array+Helpers.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import Foundation
extension Array {
func allPairs() -> [[Element]] {
return self.enumerated().flatMap { (index, first) in
self[index..<self.endIndex].dropFirst().map { [first, $0] }
}
}
}

View File

@ -0,0 +1,137 @@
//
// AssetComparisonViewController.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
class AssetComparisonViewController: UIViewController {
let allAssets: [PHAsset]
var assetScores = [String: Int]()
let assetPairs: [[PHAsset]]
var currentPairIndex: Int = 0 {
didSet {
currentAssetPair = assetPairs[currentPairIndex]
currentlySelected = 0
}
}
var currentlySelected: Int = 0 {
didSet {
largeImage.image = currentImagePair[currentlySelected]
}
}
var currentAssetPair: [PHAsset]! {
didSet {
loadImages()
}
}
var currentImagePair: [UIImage?] = [nil, nil] {
didSet {
largeImage.image = currentImagePair[currentlySelected]
}
}
let largeImage = LargeImageViewController()
@IBOutlet var imageContainerView: UIView!
init(assets: [PHAsset]) {
guard assets.count >= 2 else { fatalError("AssetComparisonViewController requires at least 2 assets") }
self.allAssets = assets
self.assetPairs = assets.allPairs()
self.currentAssetPair = assetPairs.first!
super.init(nibName: "AssetComparisonViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
largeImage.view.translatesAutoresizingMaskIntoConstraints = false
imageContainerView.addSubview(largeImage.view)
NSLayoutConstraint.activate([
largeImage.view.leadingAnchor.constraint(equalTo: imageContainerView.leadingAnchor),
largeImage.view.topAnchor.constraint(equalTo: imageContainerView.topAnchor),
imageContainerView.trailingAnchor.constraint(equalTo: largeImage.view.trailingAnchor),
imageContainerView.bottomAnchor.constraint(equalTo: largeImage.view.bottomAnchor),
])
addChild(largeImage)
toolbarItems = [
UIBarButtonItem(title: "Skip", style: .plain, target: self, action: #selector(moveToNextPair)),
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
UIBarButtonItem(title: "Switch", style: .plain, target: self, action: #selector(switchButtonPressed)),
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
UIBarButtonItem(title: "Pick", style: .plain, target: self, action: #selector(pickButtonPressed))
]
loadImages()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController!.isToolbarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController!.isToolbarHidden = true
}
func loadImages() {
self.currentlySelected = 0
self.currentImagePair = [nil, nil]
PhotosHelper.requestFullSizeImage(for: currentAssetPair[0]) { (image) in
DispatchQueue.main.async {
self.currentImagePair = [image, self.currentImagePair[1]]
}
}
PhotosHelper.requestFullSizeImage(for: currentAssetPair[1]) { (image) in
DispatchQueue.main.async {
self.currentImagePair = [self.currentImagePair[0], image]
}
}
}
@objc func switchButtonPressed() {
currentlySelected = currentlySelected == 0 ? 1 : 0
}
@objc func moveToNextPair() {
if currentPairIndex == assetPairs.count - 1 {
print("finished: \(assetScores)")
// sorts assets by score in descending order
let sorted = allAssets
.map { ($0, assetScores[$0.localIdentifier] ?? 0) }
.sorted { (first, second) -> Bool in
return first.1 > second.1
}
let rankings = RankingsTableViewController(rankings: sorted)
show(rankings, sender: nil)
} else {
currentPairIndex += 1
}
}
@objc func pickButtonPressed() {
let identifier = currentAssetPair[currentlySelected].localIdentifier
let current = assetScores[identifier] ?? 0
assetScores[identifier] = current + 1
moveToNextPair()
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14845" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14799.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AssetComparisonViewController" customModule="PhotoRank" customModuleProvider="target">
<connections>
<outlet property="imageContainerView" destination="Sjl-dv-Pzi" id="CYZ-X3-8MF"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Sjl-dv-Pzi">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Sjl-dv-Pzi" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="3B4-cg-ar5"/>
<constraint firstAttribute="trailing" secondItem="Sjl-dv-Pzi" secondAttribute="trailing" id="4Zd-Gu-21d"/>
<constraint firstItem="Sjl-dv-Pzi" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="QUe-M1-mc9"/>
<constraint firstAttribute="bottom" secondItem="Sjl-dv-Pzi" secondAttribute="bottom" id="tMN-WK-iHt"/>
</constraints>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<point key="canvasLocation" x="139" y="115"/>
</view>
</objects>
</document>

View File

@ -0,0 +1,149 @@
//
// LargeImageViewController.swift
// Tusker
//
// Created by Shadowfacts on 8/31/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
class LargeImageViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
var image: UIImage? {
didSet {
imageView.image = image
if oldValue == nil, let image = image {
imageView.bounds = CGRect(origin: .zero, size: image.size)
view.setNeedsLayout()
}
}
}
var prevZoomScale: CGFloat?
init() {
super.init(nibName: "LargeImageViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// imageView.image = image
scrollView.delegate = self
// imageView.bounds = CGRect(origin: .zero, size: imageView.image!.size)
// view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(scrollViewPressed(_:))))
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:)))
doubleTap.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTap)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
let heightScale = maxHeight / imageView.bounds.height
let widthScale = view.bounds.width / imageView.bounds.width
let minScale = min(widthScale, heightScale)
scrollView.minimumZoomScale = minScale
scrollView.zoomScale = minScale
scrollView.maximumZoomScale = minScale * 4
centerImage()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
centerImage()
// let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale
// if scrollView.zoomScale <= scrollView.minimumZoomScale {
// controlsVisible = true
// } else if scrollView.zoomScale > prevZoomScale {
// controlsVisible = false
// }
// self.prevZoomScale = scrollView.zoomScale
}
func centerImage() {
let yOffset = max(0, (view.bounds.size.height - imageView.frame.height - scrollView.contentInset.top - scrollView.contentInset.bottom) / 2)
imageViewTopConstraint.constant = yOffset
let xOffset = max(0, (view.bounds.size.width - imageView.frame.width - scrollView.contentInset.left - scrollView.contentInset.right) / 2)
imageViewLeadingConstraint.constant = xOffset
}
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
var zoomRect = CGRect.zero
zoomRect.size.width = imageView.frame.width / scale
zoomRect.size.height = imageView.frame.height / scale
let newCenter = scrollView.convert(center, to: imageView)
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
return zoomRect
}
func animateZoomOut() {
UIView.animate(withDuration: 0.3, animations: {
self.scrollView.zoomScale = self.scrollView.minimumZoomScale
self.view.layoutIfNeeded()
})
}
// @objc func scrollViewPressed(_ sender: UITapGestureRecognizer) {
// if scrollView.zoomScale > scrollView.minimumZoomScale {
// animateZoomOut()
// } else {
//// setControlsVisible(!controlsVisible, animated: true)
// }
// }
@objc func scrollViewDoubleTapped(_ recognizer: UITapGestureRecognizer) {
if scrollView.zoomScale <= scrollView.minimumZoomScale {
let point = recognizer.location(in: recognizer.view)
let scale: CGFloat
if scrollView.minimumZoomScale < 1 {
if 1 - scrollView.zoomScale <= 0.5 {
scale = scrollView.zoomScale + 1
} else {
scale = 1
}
} else {
scale = scrollView.maximumZoomScale
}
let rect = zoomRectFor(scale: scale, center: point)
UIView.animate(withDuration: 0.3) {
self.scrollView.zoom(to: rect, animated: false)
self.view.layoutIfNeeded()
}
} else {
animateZoomOut()
}
}
// @IBAction func closeButtonPressed(_ sender: Any) {
// dismiss(animated: true)
// }
//
// @IBAction func sharePressed(_ sender: Any) {
// guard let image = image else { return }
// let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
// present(activityVC, animated: true)
// }
}

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14845" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14799.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="LargeImageViewController" customModule="PhotoRank" customModuleProvider="target">
<connections>
<outlet property="imageView" destination="qcn-1t-3sS" id="Q01-G2-y1c"/>
<outlet property="imageViewLeadingConstraint" destination="bI3-V8-M70" id="nIe-xI-E9u"/>
<outlet property="imageViewTopConstraint" destination="tfL-hp-2I2" id="EDV-RO-pTe"/>
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
<outlet property="view" destination="BJw-5C-9nT" id="1C2-VA-mNf"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="BJw-5C-9nT">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS">
<rect key="frame" x="0.0" y="-10" width="375" height="647"/>
<gestureRecognizers/>
</imageView>
</subviews>
<gestureRecognizers/>
<constraints>
<constraint firstItem="qcn-1t-3sS" firstAttribute="leading" secondItem="Skj-xq-AgQ" secondAttribute="leading" id="bI3-V8-M70"/>
<constraint firstItem="qcn-1t-3sS" firstAttribute="top" secondItem="Skj-xq-AgQ" secondAttribute="top" id="tfL-hp-2I2"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="Skj-xq-AgQ" firstAttribute="centerY" secondItem="BJw-5C-9nT" secondAttribute="centerY" id="0Xb-ib-2hg"/>
<constraint firstItem="Skj-xq-AgQ" firstAttribute="centerX" secondItem="BJw-5C-9nT" secondAttribute="centerX" id="KMe-Zc-NZq"/>
<constraint firstItem="Skj-xq-AgQ" firstAttribute="width" secondItem="BJw-5C-9nT" secondAttribute="width" id="Onj-l9-fBu"/>
<constraint firstItem="Skj-xq-AgQ" firstAttribute="height" secondItem="BJw-5C-9nT" secondAttribute="height" id="jvz-QW-n9c"/>
</constraints>
<viewLayoutGuide key="safeArea" id="w1g-VC-Ll9"/>
<point key="canvasLocation" x="-164" y="476"/>
</view>
</objects>
</document>

View File

@ -0,0 +1,45 @@
//
// AssetCollectionViewCell.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
class AssetCollectionViewCell: UICollectionViewCell {
var assetIdentifier: String!
var thumbnailImage: UIImage? {
didSet {
imageView.image = thumbnailImage
}
}
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var visualEffectView: UIVisualEffectView!
override var isSelected: Bool {
didSet {
visualEffectView.isHidden = !isSelected
}
}
override func awakeFromNib() {
super.awakeFromNib()
visualEffectView.layer.masksToBounds = true
visualEffectView.layer.cornerRadius = 12
visualEffectView.isHidden = true
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
}
}

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="AssetCollectionViewCell" customModule="PhotoRank" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="72u-oA-Xgb">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
</imageView>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0gA-uG-aFo">
<rect key="frame" x="52" y="4" width="24" height="24"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="IPu-KR-7jl">
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="checkmark.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="kxh-zz-34Y">
<rect key="frame" x="0.0" y="0.5" width="24" height="23.5"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="kxh-zz-34Y" secondAttribute="trailing" id="3cp-19-zeM"/>
<constraint firstItem="kxh-zz-34Y" firstAttribute="leading" secondItem="IPu-KR-7jl" secondAttribute="leading" id="52v-uP-gQi"/>
<constraint firstItem="kxh-zz-34Y" firstAttribute="top" secondItem="IPu-KR-7jl" secondAttribute="top" id="P1Y-M3-cYf"/>
<constraint firstAttribute="bottom" secondItem="kxh-zz-34Y" secondAttribute="bottom" id="qq7-Mn-9V1"/>
</constraints>
</view>
<constraints>
<constraint firstAttribute="width" constant="24" id="Dfp-qR-lpk"/>
<constraint firstAttribute="height" constant="24" id="vWb-4U-qx6"/>
</constraints>
<blurEffect style="regular"/>
</visualEffectView>
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="0gA-uG-aFo" secondAttribute="trailing" constant="4" id="13X-9d-65f"/>
<constraint firstAttribute="trailing" secondItem="72u-oA-Xgb" secondAttribute="trailing" id="FRb-Xl-kha"/>
<constraint firstItem="72u-oA-Xgb" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="HSg-2m-ibv"/>
<constraint firstAttribute="bottom" secondItem="72u-oA-Xgb" secondAttribute="bottom" id="Ifb-pe-ay0"/>
<constraint firstItem="0gA-uG-aFo" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="4" id="iYA-Jd-K9D"/>
<constraint firstItem="72u-oA-Xgb" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="qwp-L5-gZT"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<connections>
<outlet property="imageView" destination="72u-oA-Xgb" id="giL-3C-x8g"/>
<outlet property="visualEffectView" destination="0gA-uG-aFo" id="OPm-n4-jDw"/>
</connections>
<point key="canvasLocation" x="139" y="153"/>
</collectionViewCell>
</objects>
<resources>
<image name="checkmark.circle.fill" catalog="system" width="64" height="62"/>
</resources>
</document>

View File

@ -0,0 +1,152 @@
//
// AssetCollectionViewController.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
private let reuseIdentifier = "AssetCell"
class AssetPickerViewController: UICollectionViewController {
var continueButton: UIBarButtonItem!
var flowLayout: UICollectionViewFlowLayout {
return collectionViewLayout as! UICollectionViewFlowLayout
}
var availableWidth: CGFloat!
var thumbnailSize: CGSize!
var fetchResult: PHFetchResult<PHAsset>!
init() {
super.init(collectionViewLayout: UICollectionViewFlowLayout())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.alwaysBounceVertical = true
// Register cell classes
self.collectionView!.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier)
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
// get all photos except live photos
options.predicate = NSPredicate(format: "mediaType == %d && !((mediaSubtypes & %d) == %d)", PHAssetMediaType.image.rawValue, PHAssetMediaSubtype.photoLive.rawValue, PHAssetMediaSubtype.photoLive.rawValue)
fetchResult = PHAsset.fetchAssets(with: options)
collectionView.allowsMultipleSelection = true
setEditing(true, animated: false)
continueButton = UIBarButtonItem(title: "Continue", style: .done, target: self, action: #selector(continueButtonPressed))
updateItemsSelected()
navigationItem.rightBarButtonItem = continueButton
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let availableWidth = view.bounds.inset(by: view.safeAreaInsets).width
if self.availableWidth != availableWidth {
self.availableWidth = availableWidth
let size = (availableWidth - 8) / 3
flowLayout.itemSize = CGSize(width: size, height: size)
flowLayout.minimumInteritemSpacing = 4
flowLayout.minimumLineSpacing = 4
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let scale = UIScreen.main.scale
let cellSize = flowLayout.itemSize
thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale)
}
func updateItemsSelected() {
let selected = collectionView.indexPathsForSelectedItems?.count ?? 0
continueButton.isEnabled = selected >= 2
navigationItem.title = "\(selected) selected"
}
@objc func continueButtonPressed() {
let indexPaths = collectionView.indexPathsForSelectedItems!
let assets = fetchResult.objects(at: IndexSet(indexPaths.map { $0.item }))
let comparison = AssetComparisonViewController(assets: assets)
show(comparison, sender: nil)
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return fetchResult.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! AssetCollectionViewCell
let asset = fetchResult.object(at: indexPath.row)
cell.assetIdentifier = asset.localIdentifier
PhotosHelper.requestThumbnail(for: asset, targetSize: thumbnailSize) { (image) in
DispatchQueue.main.async {
if cell.assetIdentifier == asset.localIdentifier {
cell.thumbnailImage = image
}
}
}
return cell
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
updateItemsSelected()
}
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
updateItemsSelected()
}
/*
// Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
}
*/
}

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,79 @@
//
// RankingTableViewCell.swift
// PhotoRank
//
// Created by Shadowfacts on 8/29/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
class RankingTableViewCell: UITableViewCell {
@IBOutlet weak var assetImageView: UIImageView!
@IBOutlet weak var scoreLabel: UILabel!
@IBOutlet weak var favoriteButton: UIButton!
var assetIdentifier: String?
override func awakeFromNib() {
super.awakeFromNib()
assetImageView.layer.masksToBounds = true
assetImageView.layer.cornerRadius = 8
}
func updateUI(asset: PHAsset, score: Int) {
self.assetIdentifier = asset.localIdentifier
PhotosHelper.requestThumbnail(for: asset, targetSize: assetImageView.bounds.size) { (image) in
guard self.assetIdentifier == asset.localIdentifier else { return }
self.assetImageView.image = image
}
scoreLabel.text = "Score: \(score)"
updateFavoriteButton(favorited: asset.isFavorite)
}
private func updateFavoriteButton(favorited: Bool) {
let name = favorited ? "heart.fill" : "heart"
let image = UIImage(systemName: name)
favoriteButton.setImage(image, for: .normal)
}
@IBAction func favoriteButtonPressed() {
let generator = UINotificationFeedbackGenerator()
generator.prepare()
guard let assetIdentifier = assetIdentifier else {
generator.notificationOccurred(.error)
return
}
let result = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil)
guard result.count == 1 else {
generator.notificationOccurred(.error)
return
}
let asset = result.object(at: 0)
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest(for: asset)
request.isFavorite = !asset.isFavorite
}) { (success, error) in
if success {
generator.notificationOccurred(.success)
DispatchQueue.main.async {
guard self.assetIdentifier == assetIdentifier else { return }
self.updateFavoriteButton(favorited: !asset.isFavorite)
}
} else {
generator.notificationOccurred(.error)
print("Error ocurred changing favorite: \(String(describing: error))")
}
}
}
}

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="116" id="KGk-i7-Jjw" customClass="RankingTableViewCell" customModule="PhotoRank" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="96"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="OsB-61-ZJs">
<rect key="frame" x="8" y="8" width="80" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="PeH-tP-ooH"/>
<constraint firstAttribute="width" constant="80" id="VCj-Je-0sM"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Score: 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xba-9D-I33">
<rect key="frame" x="104" y="37.5" width="61" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F4t-7A-TLx">
<rect key="frame" x="284.5" y="37" width="19.5" height="22"/>
<state key="normal" image="heart" catalog="system"/>
<connections>
<action selector="favoriteButtonPressed" destination="KGk-i7-Jjw" eventType="touchUpInside" id="9Ar-jD-N0y"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="xba-9D-I33" firstAttribute="leading" secondItem="OsB-61-ZJs" secondAttribute="trailing" constant="16" id="Iyl-7T-iy4"/>
<constraint firstItem="F4t-7A-TLx" firstAttribute="trailing" secondItem="H2p-sc-9uM" secondAttribute="trailingMargin" id="OVA-8T-N7P"/>
<constraint firstItem="F4t-7A-TLx" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="cjb-mc-1ff"/>
<constraint firstItem="OsB-61-ZJs" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="8" id="fRg-ac-ljI"/>
<constraint firstAttribute="bottom" secondItem="OsB-61-ZJs" secondAttribute="bottom" constant="8" id="jPG-Uj-1aL"/>
<constraint firstItem="OsB-61-ZJs" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="lOa-Y4-gxE"/>
<constraint firstItem="xba-9D-I33" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="sGL-MZ-Db9"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="assetImageView" destination="OsB-61-ZJs" id="HQe-4O-cNI"/>
<outlet property="favoriteButton" destination="F4t-7A-TLx" id="LJM-ZH-fX6"/>
<outlet property="scoreLabel" destination="xba-9D-I33" id="4Xh-j1-796"/>
</connections>
<point key="canvasLocation" x="131.8840579710145" y="148.66071428571428"/>
</tableViewCell>
</objects>
<resources>
<image name="heart" catalog="system" width="64" height="58"/>
</resources>
</document>

View File

@ -0,0 +1,111 @@
//
// RankingsTableViewController.swift
// PhotoRank
//
// Created by Shadowfacts on 8/5/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
private let reuseIdentifier = "AssetScoreCell"
class RankingsTableViewController: UITableViewController {
var rankings: [(PHAsset, Int)]
init(rankings: [(PHAsset, Int)]) {
self.rankings = rankings
super.init(nibName: "RankingsTableViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "RankingTableViewCell", bundle: .main), forCellReuseIdentifier: reuseIdentifier)
tableView.rowHeight = 96
tableView.alwaysBounceVertical = true
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rankings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! RankingTableViewCell
let (asset, score) = rankings[indexPath.row]
cell.updateUI(asset: asset, score: score)
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
let (asset, _) = rankings[indexPath.row]
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.deleteAssets(NSArray(object: asset))
}) { (success, error) in
let generator = UINotificationFeedbackGenerator()
if success {
generator.notificationOccurred(.success)
DispatchQueue.main.async {
self.rankings.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
} else {
generator.notificationOccurred(.error)
print("Error occurred deleting asset: \(String(describing: error))")
}
}
}
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="RankingsTableViewController" customModule="PhotoRank" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
<connections>
<outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
<outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
</connections>
<point key="canvasLocation" x="133" y="154"/>
</tableView>
</objects>
</document>

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app must access your photo library to rank photos.</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
@ -22,8 +24,6 @@
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
@ -31,8 +31,8 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>

View File

@ -0,0 +1,30 @@
//
// PhotosHelper.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Photos
struct PhotosHelper {
private static let imageManager = PHCachingImageManager()
private init() {}
static func requestFullSizeImage(for asset: PHAsset, completion: @escaping (UIImage) -> Void) {
imageManager.requestImageData(for: asset, options: nil) { (data, _, _, _) in
guard let data = data, let image = UIImage(data: data) else { fatalError() }
completion(image)
}
}
static func requestThumbnail(for asset: PHAsset, targetSize: CGSize, completion: @escaping (UIImage) -> Void) {
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: nil) { (image, _) in
guard let image = image else { return }
completion(image)
}
}
}

View File

@ -0,0 +1,30 @@
//
// RootViewController.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
class RootViewController: UIViewController {
init() {
super.init(nibName: "RootViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = "PhotoRank"
}
@IBAction func choosePhotosPressed() {
show(AssetPickerViewController(), sender: nil)
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14845" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14799.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="RootViewController" customModule="PhotoRank" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WEK-nT-whA">
<rect key="frame" x="154.5" y="433" width="105" height="30"/>
<state key="normal" title="Choose Photos"/>
<connections>
<action selector="choosePhotosPressed" destination="-1" eventType="touchUpInside" id="njJ-qR-beN"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="WEK-nT-whA" firstAttribute="centerY" secondItem="i5M-Pr-FkT" secondAttribute="centerY" id="PJ3-tz-HJ6"/>
<constraint firstItem="WEK-nT-whA" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="YDs-MY-anw"/>
</constraints>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<point key="canvasLocation" x="139" y="153"/>
</view>
</objects>
</document>

View File

@ -1,20 +0,0 @@
//
// ViewController.swift
// PhotoRank
//
// Created by Shadowfacts on 8/3/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}