Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
tankista committed Apr 18, 2018
2 parents cd59ef9 + dd6bb87 commit cabbc4f
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 49 deletions.
26 changes: 25 additions & 1 deletion ImagePicker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
427926021F96407900B6D55F /* LivePhotoCameraCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 427926001F963E9C00B6D55F /* LivePhotoCameraCell.xib */; };
42990DBF1FA07AF7001658C4 /* RecordDurationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42990DBE1FA07AF7001658C4 /* RecordDurationLabel.swift */; };
429BFDAA1F68161D00029440 /* ImagePickerAssetModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429BFDA91F68161D00029440 /* ImagePickerAssetModel.swift */; };
429CCBD02080D62F0050078B /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBCF2080D62F0050078B /* AsynchronousOperation.swift */; };
429CCBD22080D6620050078B /* CollectionViewBatchAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBD12080D6620050078B /* CollectionViewBatchAnimation.swift */; };
429CCBD42080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBD32080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift */; };
42A037E01F66C9E700534350 /* CustomVideoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 42A037DF1F66C9E700534350 /* CustomVideoCell.xib */; };
42B296A91FB1CC3200590260 /* ImagePickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 42B296A81FB1CC3200590260 /* ImagePickerView.xib */; };
42B296AB1FB1CC7400590260 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B296AA1FB1CC7400590260 /* ImagePickerView.swift */; };
Expand Down Expand Up @@ -124,6 +127,9 @@
427926001F963E9C00B6D55F /* LivePhotoCameraCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LivePhotoCameraCell.xib; sourceTree = "<group>"; };
42990DBE1FA07AF7001658C4 /* RecordDurationLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordDurationLabel.swift; sourceTree = "<group>"; };
429BFDA91F68161D00029440 /* ImagePickerAssetModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerAssetModel.swift; sourceTree = "<group>"; };
429CCBCF2080D62F0050078B /* AsynchronousOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = "<group>"; };
429CCBD12080D6620050078B /* CollectionViewBatchAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewBatchAnimation.swift; sourceTree = "<group>"; };
429CCBD32080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewUpdatesCoordinator.swift; sourceTree = "<group>"; };
42A037DF1F66C9E700534350 /* CustomVideoCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CustomVideoCell.xib; sourceTree = "<group>"; };
42B296A81FB1CC3200590260 /* ImagePickerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImagePickerView.xib; sourceTree = "<group>"; };
42B296AA1FB1CC7400590260 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -196,6 +202,7 @@
isa = PBXGroup;
children = (
420C24181F5D82F9008935D4 /* ImagePicker.h */,
42887C072080D5D200194155 /* Operations */,
42D7036F1F7909100057D557 /* Public */,
42D703701F790A3F0057D557 /* Media */,
42990DBD1FA07A6C001658C4 /* Views */,
Expand Down Expand Up @@ -227,6 +234,16 @@
path = "Custom Views";
sourceTree = "<group>";
};
42887C072080D5D200194155 /* Operations */ = {
isa = PBXGroup;
children = (
429CCBCF2080D62F0050078B /* AsynchronousOperation.swift */,
429CCBD12080D6620050078B /* CollectionViewBatchAnimation.swift */,
429CCBD32080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift */,
);
name = Operations;
sourceTree = "<group>";
};
42990DBD1FA07A6C001658C4 /* Views */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -332,7 +349,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = Inloop;
TargetAttributes = {
420C23FB1F5D82C6008935D4 = {
Expand Down Expand Up @@ -415,6 +432,8 @@
buildActionMask = 2147483647;
files = (
421442631F6014B3006BA45A /* CellRegistrator.swift in Sources */,
429CCBD22080D6620050078B /* CollectionViewBatchAnimation.swift in Sources */,
429CCBD42080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift in Sources */,
420C24361F5ED4AB008935D4 /* ImagePickerLayout.swift in Sources */,
42D0979C1F7BF6B300A66E33 /* AssetCell.swift in Sources */,
42E736521F8510B70060E24D /* VideoCaptureDelegate.swift in Sources */,
Expand All @@ -431,6 +450,7 @@
427925F51F96381400B6D55F /* ShutterButton.swift in Sources */,
42EDD4061F71261600EAD2F5 /* AVPreviewView.swift in Sources */,
420C24411F5EEA3C008935D4 /* LayoutConfiguration.swift in Sources */,
429CCBD02080D62F0050078B /* AsynchronousOperation.swift in Sources */,
427925FD1F963A8D00B6D55F /* VideoCameraCell.swift in Sources */,
42EDD4161F712B2A00EAD2F5 /* Miscellaneous.swift in Sources */,
427926011F963E9C00B6D55F /* LivePhotoCameraCell.swift in Sources */,
Expand Down Expand Up @@ -490,13 +510,15 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand Down Expand Up @@ -546,13 +568,15 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -37,7 +36,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
4 changes: 2 additions & 2 deletions ImagePicker/ActionCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ extension ActionCell {
switch index {
case 0:
titleLabel.text = "Camera"
imageView.image = #imageLiteral(resourceName: "button-camera")
imageView.image = UIImage(named: "button-camera", in: Bundle(for: type(of: self)), compatibleWith: nil)
case 1:
titleLabel.text = "Photos"
imageView.image = #imageLiteral(resourceName: "button-photo-library")
imageView.image = UIImage(named: "button-photo-library", in: Bundle(for: type(of: self)), compatibleWith: nil)
default: break
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"author" : "xcode"
},
"properties" : {
"compression-type" : "gpu-optimized-smallest",
"template-rendering-intent" : "original"
}
}
61 changes: 61 additions & 0 deletions ImagePicker/AsynchronousOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// AsynchronousOperation.swift
// ImagePicker
//
// Created by Peter Stajger on 13/04/2018.
// Copyright © 2018 Inloop. All rights reserved.
//

import Foundation

///
/// Provides primitives for wrapping Operation with asynchronous code that can
/// be enqueued in a OperationQueue and run serially.
///
class AsynchronousOperation : Foundation.Operation {

var stateFinished: Bool = false {
willSet { willChangeValue(forKey: "isFinished") }
didSet { didChangeValue(forKey: "isFinished") }
}
var stateExecuting: Bool = false {
willSet { willChangeValue(forKey: "isExecuting") }
didSet { didChangeValue(forKey: "isExecuting") }
}

override var isFinished: Bool {
return stateFinished
}

override var isExecuting: Bool {
return stateExecuting
}

override var isAsynchronous: Bool {
return true
}

override func main() {
if isCancelled {
completeOperation()
}
else {
stateExecuting = true
execute()
}
}

func execute() {
fatalError("This method has to be overriden")
}

func completeOperation() {
if self.stateExecuting == true {
self.stateExecuting = false
}

if self.stateFinished == false {
self.stateFinished = true
}
}
}
2 changes: 1 addition & 1 deletion ImagePicker/CarvedLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ final class CarvedLabel : UIView {
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
path.fill()

guard let context = UIGraphicsGetCurrentContext(), (text?.characters.count ?? 0) > 0 else {
guard let context = UIGraphicsGetCurrentContext(), (text?.count ?? 0) > 0 else {
return
}

Expand Down
48 changes: 48 additions & 0 deletions ImagePicker/CollectionViewBatchAnimation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// CollectionViewBatchAnimation.swift
// ImagePicker
//
// Created by Peter Stajger on 13/04/2018.
// Copyright © 2018 Inloop. All rights reserved.
//

import UIKit
import Photos

///
/// Wraps collectionView's `performBatchUpdates` block into AsynchronousOperation.
///
final class CollectionViewBatchAnimation<ObjectType> : AsynchronousOperation where ObjectType : PHObject {
private let collectionView: UICollectionView
private let sectionIndex: Int
private let changes: PHFetchResultChangeDetails<ObjectType>

init(collectionView: UICollectionView, sectionIndex: Int, changes: PHFetchResultChangeDetails<ObjectType>) {
self.collectionView = collectionView
self.sectionIndex = sectionIndex
self.changes = changes
}

override func execute() {
// If we have incremental diffs, animate them in the collection view
collectionView.performBatchUpdates({ [unowned self] in

// For indexes to make sense, updates must be in this order:
// delete, insert, reload, move
if let removed = self.changes.removedIndexes, removed.isEmpty == false {
self.collectionView.deleteItems(at: removed.map({ IndexPath(item: $0, section: self.sectionIndex) }))
}
if let inserted = changes.insertedIndexes, inserted.isEmpty == false {
self.collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: self.sectionIndex) }))
}
if let changed = changes.changedIndexes, changed.isEmpty == false {
self.collectionView.reloadItems(at: changed.map({ IndexPath(item: $0, section: self.sectionIndex) }))
}
changes.enumerateMoves { fromIndex, toIndex in
self.collectionView.moveItem(at: IndexPath(item: fromIndex, section: self.sectionIndex), to: IndexPath(item: toIndex, section: self.sectionIndex))
}
}, completion: { finished in
self.completeOperation()
})
}
}
52 changes: 52 additions & 0 deletions ImagePicker/CollectionViewUpdatesCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// CollectionViewUpdatesCoordinator.swift
// ImagePicker
//
// Created by Peter Stajger on 13/04/2018.
// Copyright © 2018 Inloop. All rights reserved.
//

import UIKit
import Photos

///
/// Makes sure that all updates are performed in a serial queue, especially batch animations. This
/// will make sure that reloadData() will never be called durring batch updates animations, which
/// will prevent collection view from crashing on internal incosistency.
///
final class CollectionViewUpdatesCoordinator {
deinit {
log("deinit: \(String(describing: self))")
}

private let collectionView: UICollectionView

private var serialMainQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = DispatchQueue.main
return queue
}()

init(collectionView: UICollectionView) {
self.collectionView = collectionView
}

/// Provides opportunuty to update collectionView's dataSource in underlaying queue.
func performDataSourceUpdate(updates: @escaping ()->Void) {
serialMainQueue.addOperation(updates)
}

/// Updates collection view.
func performChanges<PHAsset>(_ changes: PHFetchResultChangeDetails<PHAsset>, inSection: Int) {
if changes.hasIncrementalChanges {
let operation = CollectionViewBatchAnimation(collectionView: collectionView, sectionIndex: inSection, changes: changes)
serialMainQueue.addOperation(operation)
}
else {
serialMainQueue.addOperation { [unowned self] in
self.collectionView.reloadData()
}
}
}
}

0 comments on commit cabbc4f

Please sign in to comment.