PhotoProgress
|
@ -0,0 +1,42 @@
|
|||
Sample code project: PhotoProgress: Using NSProgress
|
||||
Version: 1.0
|
||||
|
||||
IMPORTANT: This Apple software is supplied to you by Apple
|
||||
Inc. ("Apple") in consideration of your agreement to the following
|
||||
terms, and your use, installation, modification or redistribution of
|
||||
this Apple software constitutes acceptance of these terms. If you do
|
||||
not agree with these terms, please do not use, install, modify or
|
||||
redistribute this Apple software.
|
||||
|
||||
In consideration of your agreement to abide by the following terms, and
|
||||
subject to these terms, Apple grants you a personal, non-exclusive
|
||||
license, under Apple's copyrights in this original Apple software (the
|
||||
"Apple Software"), to use, reproduce, modify and redistribute the Apple
|
||||
Software, with or without modifications, in source and/or binary forms;
|
||||
provided that if you redistribute the Apple Software in its entirety and
|
||||
without modifications, you must retain this notice and the following
|
||||
text and disclaimers in all such redistributions of the Apple Software.
|
||||
Neither the name, trademarks, service marks or logos of Apple Inc. may
|
||||
be used to endorse or promote products derived from the Apple Software
|
||||
without specific prior written permission from Apple. Except as
|
||||
expressly stated in this notice, no other rights or licenses, express or
|
||||
implied, are granted by Apple herein, including but not limited to any
|
||||
patent rights that may be infringed by your derivative works or by other
|
||||
works in which the Apple Software may be incorporated.
|
||||
|
||||
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
|
||||
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
|
||||
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
|
||||
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
|
||||
|
||||
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
|
||||
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
|
||||
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
|
||||
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
|
@ -0,0 +1,351 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
312329641B0FF89800C3BE18 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312329601B0FF89800C3BE18 /* Album.swift */; };
|
||||
312329651B0FF89800C3BE18 /* PhotoDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312329611B0FF89800C3BE18 /* PhotoDownload.swift */; };
|
||||
312329661B0FF89800C3BE18 /* PhotoFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312329621B0FF89800C3BE18 /* PhotoFilter.swift */; };
|
||||
312329671B0FF89800C3BE18 /* PhotoImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312329631B0FF89800C3BE18 /* PhotoImport.swift */; };
|
||||
314AEFFD1B1006CE00282B5A /* PhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314AEFFC1B1006CE00282B5A /* PhotoCollectionViewCell.swift */; };
|
||||
3157CF961B0FCFE30012CA05 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3157CF951B0FCFE30012CA05 /* AppDelegate.swift */; };
|
||||
3157CF981B0FCFE30012CA05 /* PhotosCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3157CF971B0FCFE30012CA05 /* PhotosCollectionViewController.swift */; };
|
||||
3157CF9B1B0FCFE30012CA05 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3157CF991B0FCFE30012CA05 /* Main.storyboard */; };
|
||||
3157CF9D1B0FCFE30012CA05 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3157CF9C1B0FCFE30012CA05 /* Images.xcassets */; };
|
||||
3157CFA01B0FCFE30012CA05 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3157CF9E1B0FCFE30012CA05 /* LaunchScreen.storyboard */; };
|
||||
BF1317431B1657BA005E86BE /* Photos in Resources */ = {isa = PBXBuildFile; fileRef = BF1317411B1657BA005E86BE /* Photos */; };
|
||||
BF1334571B16895900A1ABE7 /* PhotosViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1334561B16895900A1ABE7 /* PhotosViewController.swift */; };
|
||||
BFED0B6F1B166F8B006F80C3 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFED0B6E1B166F8B006F80C3 /* Photo.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
312329601B0FF89800C3BE18 /* Album.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
|
||||
312329611B0FF89800C3BE18 /* PhotoDownload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoDownload.swift; sourceTree = "<group>"; };
|
||||
312329621B0FF89800C3BE18 /* PhotoFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFilter.swift; sourceTree = "<group>"; };
|
||||
312329631B0FF89800C3BE18 /* PhotoImport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoImport.swift; sourceTree = "<group>"; };
|
||||
314AEFFC1B1006CE00282B5A /* PhotoCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
3157CF921B0FCFE30012CA05 /* PhotoProgress.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoProgress.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3157CF951B0FCFE30012CA05 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
3157CF971B0FCFE30012CA05 /* PhotosCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotosCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
3157CF9A1B0FCFE30012CA05 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
3157CF9C1B0FCFE30012CA05 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
3157CF9F1B0FCFE30012CA05 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
3157CFA11B0FCFE30012CA05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
3E87FC771B1E623000B07A78 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
BF1317411B1657BA005E86BE /* Photos */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Photos; path = PhotoProgress/Photos; sourceTree = SOURCE_ROOT; };
|
||||
BF1334561B16895900A1ABE7 /* PhotosViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosViewController.swift; sourceTree = "<group>"; };
|
||||
BFED0B6E1B166F8B006F80C3 /* Photo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
3157CF8F1B0FCFE30012CA05 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3157CF891B0FCFE30012CA05 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3E87FC771B1E623000B07A78 /* README.md */,
|
||||
3157CF941B0FCFE30012CA05 /* PhotoProgress */,
|
||||
3157CF931B0FCFE30012CA05 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3157CF931B0FCFE30012CA05 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3157CF921B0FCFE30012CA05 /* PhotoProgress.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3157CF941B0FCFE30012CA05 /* PhotoProgress */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3157CF951B0FCFE30012CA05 /* AppDelegate.swift */,
|
||||
BFED0B701B167049006F80C3 /* View Controllers */,
|
||||
BFED0B6D1B166F73006F80C3 /* Album */,
|
||||
3157CF991B0FCFE30012CA05 /* Main.storyboard */,
|
||||
BF1317411B1657BA005E86BE /* Photos */,
|
||||
3157CF9C1B0FCFE30012CA05 /* Images.xcassets */,
|
||||
3157CF9E1B0FCFE30012CA05 /* LaunchScreen.storyboard */,
|
||||
3157CFA11B0FCFE30012CA05 /* Info.plist */,
|
||||
);
|
||||
path = PhotoProgress;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFED0B6C1B166F56006F80C3 /* Operations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
312329631B0FF89800C3BE18 /* PhotoImport.swift */,
|
||||
312329611B0FF89800C3BE18 /* PhotoDownload.swift */,
|
||||
312329621B0FF89800C3BE18 /* PhotoFilter.swift */,
|
||||
);
|
||||
name = Operations;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFED0B6D1B166F73006F80C3 /* Album */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
312329601B0FF89800C3BE18 /* Album.swift */,
|
||||
BFED0B6E1B166F8B006F80C3 /* Photo.swift */,
|
||||
BFED0B6C1B166F56006F80C3 /* Operations */,
|
||||
);
|
||||
name = Album;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFED0B701B167049006F80C3 /* View Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF1334561B16895900A1ABE7 /* PhotosViewController.swift */,
|
||||
3157CF971B0FCFE30012CA05 /* PhotosCollectionViewController.swift */,
|
||||
314AEFFC1B1006CE00282B5A /* PhotoCollectionViewCell.swift */,
|
||||
);
|
||||
name = "View Controllers";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3157CF911B0FCFE30012CA05 /* PhotoProgress */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3157CFAF1B0FCFE30012CA05 /* Build configuration list for PBXNativeTarget "PhotoProgress" */;
|
||||
buildPhases = (
|
||||
3157CF8E1B0FCFE30012CA05 /* Sources */,
|
||||
3157CF8F1B0FCFE30012CA05 /* Frameworks */,
|
||||
3157CF901B0FCFE30012CA05 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PhotoProgress;
|
||||
productName = PhotoProgress;
|
||||
productReference = 3157CF921B0FCFE30012CA05 /* PhotoProgress.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
3157CF8A1B0FCFE30012CA05 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0700;
|
||||
ORGANIZATIONNAME = "Apple, Inc.";
|
||||
TargetAttributes = {
|
||||
3157CF911B0FCFE30012CA05 = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 3157CF8D1B0FCFE30012CA05 /* Build configuration list for PBXProject "PhotoProgress" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 3157CF891B0FCFE30012CA05;
|
||||
productRefGroup = 3157CF931B0FCFE30012CA05 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3157CF911B0FCFE30012CA05 /* PhotoProgress */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
3157CF901B0FCFE30012CA05 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3157CFA01B0FCFE30012CA05 /* LaunchScreen.storyboard in Resources */,
|
||||
3157CF9B1B0FCFE30012CA05 /* Main.storyboard in Resources */,
|
||||
3157CF9D1B0FCFE30012CA05 /* Images.xcassets in Resources */,
|
||||
BF1317431B1657BA005E86BE /* Photos in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
3157CF8E1B0FCFE30012CA05 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
314AEFFD1B1006CE00282B5A /* PhotoCollectionViewCell.swift in Sources */,
|
||||
BFED0B6F1B166F8B006F80C3 /* Photo.swift in Sources */,
|
||||
BF1334571B16895900A1ABE7 /* PhotosViewController.swift in Sources */,
|
||||
312329651B0FF89800C3BE18 /* PhotoDownload.swift in Sources */,
|
||||
312329661B0FF89800C3BE18 /* PhotoFilter.swift in Sources */,
|
||||
3157CF981B0FCFE30012CA05 /* PhotosCollectionViewController.swift in Sources */,
|
||||
312329671B0FF89800C3BE18 /* PhotoImport.swift in Sources */,
|
||||
312329641B0FF89800C3BE18 /* Album.swift in Sources */,
|
||||
3157CF961B0FCFE30012CA05 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
3157CF991B0FCFE30012CA05 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3157CF9A1B0FCFE30012CA05 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3157CF9E1B0FCFE30012CA05 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3157CF9F1B0FCFE30012CA05 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
3157CFAD1B0FCFE30012CA05 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3157CFAE1B0FCFE30012CA05 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3157CFB01B0FCFE30012CA05 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = PhotoProgress/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TOOLCHAINS = default;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3157CFB11B0FCFE30012CA05 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = PhotoProgress/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TOOLCHAINS = default;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
3157CF8D1B0FCFE30012CA05 /* Build configuration list for PBXProject "PhotoProgress" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3157CFAD1B0FCFE30012CA05 /* Debug */,
|
||||
3157CFAE1B0FCFE30012CA05 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3157CFAF1B0FCFE30012CA05 /* Build configuration list for PBXNativeTarget "PhotoProgress" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3157CFB01B0FCFE30012CA05 /* Debug */,
|
||||
3157CFB11B0FCFE30012CA05 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 3157CF8A1B0FCFE30012CA05 /* Project object */;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
Album has an array of Photos loaded from the application bundle
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class Album: NSObject {
|
||||
// MARK: Properties
|
||||
|
||||
let photos: [Photo]
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init () {
|
||||
guard let imageURLs = NSBundle.mainBundle().URLsForResourcesWithExtension("jpg", subdirectory: "Photos") else {
|
||||
fatalError("Unable to load photos")
|
||||
}
|
||||
|
||||
photos = imageURLs.map { Photo(URL: $0) }
|
||||
}
|
||||
|
||||
func importPhotos() -> NSProgress {
|
||||
let progress = NSProgress()
|
||||
progress.totalUnitCount = Int64(photos.count)
|
||||
|
||||
for photo in photos {
|
||||
let importProgress = photo.startImport()
|
||||
|
||||
progress.addChild(importProgress, withPendingUnitCount: 1)
|
||||
}
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
func resetPhotos() {
|
||||
for photo in photos {
|
||||
photo.reset()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// MARK: Properties
|
||||
|
||||
var window: UIWindow?
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178r" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,238 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178r" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="eT1-6D-OLs">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Photos View Controller-->
|
||||
<scene sceneID="uEh-Sm-wmz">
|
||||
<objects>
|
||||
<viewController id="Mso-Eu-4PO" customClass="PhotosViewController" customModule="PhotoProgress" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="1E7-QS-gAv"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="TPT-7M-4PO"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="5Ef-xO-KnR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZJc-RL-hFe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="580"/>
|
||||
<animations/>
|
||||
<connections>
|
||||
<segue destination="s5Z-Tx-3S8" kind="embed" identifier="collectionView" id="gb1-9L-Jua"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<view alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Hpw-j6-tjM" userLabel="Progress Container View">
|
||||
<rect key="frame" x="0.0" y="514" width="600" height="42"/>
|
||||
<subviews>
|
||||
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Jgt-Ay-v4f">
|
||||
<rect key="frame" x="8" y="20" width="584" height="2"/>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="10" id="2hX-cf-4yu"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="2hX-cf-4yu"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</progressView>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="0.77659574468085102" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Jgt-Ay-v4f" secondAttribute="trailing" id="06v-px-EeN"/>
|
||||
<constraint firstItem="Jgt-Ay-v4f" firstAttribute="leading" secondItem="Hpw-j6-tjM" secondAttribute="leadingMargin" id="63Z-dZ-4Dy"/>
|
||||
<constraint firstAttribute="height" constant="50" id="HG2-IC-CAn"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Jgt-Ay-v4f" secondAttribute="bottom" constant="20" id="jbM-BD-pmj"/>
|
||||
<constraint firstItem="Jgt-Ay-v4f" firstAttribute="top" secondItem="Hpw-j6-tjM" secondAttribute="top" constant="20" id="tAp-Mf-sVp"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="HG2-IC-CAn"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="ZJc-RL-hFe" secondAttribute="trailing" id="Anf-Z0-bwC"/>
|
||||
<constraint firstItem="TPT-7M-4PO" firstAttribute="top" secondItem="Hpw-j6-tjM" secondAttribute="bottom" id="F3D-xE-uM8"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="trailing" secondItem="5Ef-xO-KnR" secondAttribute="trailingMargin" id="GR6-Da-Lzb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Hpw-j6-tjM" secondAttribute="trailing" id="H07-uv-yNa"/>
|
||||
<constraint firstItem="Hpw-j6-tjM" firstAttribute="leading" secondItem="5Ef-xO-KnR" secondAttribute="leading" id="JWx-DC-hU8"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ZJc-RL-hFe" secondAttribute="bottom" constant="20" symbolic="YES" id="Mn1-CE-ySb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ZJc-RL-hFe" secondAttribute="trailing" id="R1z-2w-3Pg"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="top" secondItem="5Ef-xO-KnR" secondAttribute="top" id="XeR-j8-vfr"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="top" secondItem="5Ef-xO-KnR" secondAttribute="top" id="c5Z-IW-okn"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="top" secondItem="5Ef-xO-KnR" secondAttribute="topMargin" id="edf-7x-KJf"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="leading" secondItem="5Ef-xO-KnR" secondAttribute="leading" id="ek9-Xn-uJr"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="leading" secondItem="5Ef-xO-KnR" secondAttribute="leading" id="gST-zx-FxO"/>
|
||||
<constraint firstItem="ZJc-RL-hFe" firstAttribute="leading" secondItem="5Ef-xO-KnR" secondAttribute="leadingMargin" id="xP4-Nx-MAB"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="Anf-Z0-bwC"/>
|
||||
<exclude reference="GR6-Da-Lzb"/>
|
||||
<exclude reference="XeR-j8-vfr"/>
|
||||
<exclude reference="edf-7x-KJf"/>
|
||||
<exclude reference="gST-zx-FxO"/>
|
||||
<exclude reference="xP4-Nx-MAB"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<toolbarItems>
|
||||
<barButtonItem title="Import" id="mfo-aK-2ws" userLabel="Import Toolbar Item">
|
||||
<connections>
|
||||
<action selector="startImport" destination="Mso-Eu-4PO" id="Ai3-dZ-RFQ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</toolbarItems>
|
||||
<navigationItem key="navigationItem" id="vUJ-G8-plS"/>
|
||||
<connections>
|
||||
<outlet property="cancelToolbarItem" destination="Ab3-Xy-4uA" id="I0a-C8-Qcb"/>
|
||||
<outlet property="pauseToolbarItem" destination="HKr-E4-lS4" id="AX1-jr-iMN"/>
|
||||
<outlet property="progressContainerView" destination="Hpw-j6-tjM" id="LQx-AW-BjO"/>
|
||||
<outlet property="progressView" destination="Jgt-Ay-v4f" id="lrf-h9-CdP"/>
|
||||
<outlet property="resetToolbarItem" destination="SMY-2N-Q5Y" id="h2F-zh-y6y"/>
|
||||
<outlet property="resumeToolbarItem" destination="Bmp-kJ-hNu" id="2rg-Hp-DvT"/>
|
||||
<outlet property="startToolbarItem" destination="mfo-aK-2ws" id="FKx-RJ-dhX"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="eeK-ba-Jld" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<barButtonItem title="Cancel" id="Ab3-Xy-4uA">
|
||||
<connections>
|
||||
<action selector="cancelImport" destination="Mso-Eu-4PO" id="hAC-Gc-9lR"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem title="Pause" id="HKr-E4-lS4">
|
||||
<connections>
|
||||
<action selector="pauseImport" destination="Mso-Eu-4PO" id="dLC-Oa-4bx"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem title="Resume" id="Bmp-kJ-hNu">
|
||||
<connections>
|
||||
<action selector="resumeImport" destination="Mso-Eu-4PO" id="LEM-pq-2rm"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem title="Reset" id="SMY-2N-Q5Y" userLabel="Reset Toolbar Item">
|
||||
<connections>
|
||||
<action selector="resetImport" destination="Mso-Eu-4PO" id="QEj-lr-Xqx"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1248" y="184"/>
|
||||
</scene>
|
||||
<!--Photos Collection View Controller-->
|
||||
<scene sceneID="HLW-Tb-rNQ">
|
||||
<objects>
|
||||
<collectionViewController id="s5Z-Tx-3S8" customClass="PhotosCollectionViewController" customModule="PhotoProgress" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="Cs4-xi-Dza">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="580"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="FMq-YW-MYK">
|
||||
<size key="itemSize" width="300" height="300"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Photo" id="j9d-rb-zxM" customClass="PhotoCollectionViewCell" customModule="PhotoProgress" customModuleProvider="target">
|
||||
<rect key="frame" x="150" y="0.0" width="300" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="ALQ-RY-TSP">
|
||||
<rect key="frame" x="8" y="8" width="284" height="274"/>
|
||||
<animations/>
|
||||
</imageView>
|
||||
<progressView hidden="YES" opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aFy-vJ-c96">
|
||||
<rect key="frame" x="8" y="290" width="284" height="2"/>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="10" id="7sQ-ZD-hiN"/>
|
||||
<constraint firstAttribute="height" constant="2" id="miR-J6-Zof"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="7sQ-ZD-hiN"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</progressView>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="ALQ-RY-TSP" secondAttribute="trailing" id="3Jk-2U-KEC"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="ALQ-RY-TSP" secondAttribute="bottom" constant="8" id="9u5-mU-FQu"/>
|
||||
<constraint firstItem="aFy-vJ-c96" firstAttribute="top" secondItem="ALQ-RY-TSP" secondAttribute="bottom" constant="8" symbolic="YES" id="AgT-PE-RAD"/>
|
||||
<constraint firstItem="aFy-vJ-c96" firstAttribute="leading" secondItem="j9d-rb-zxM" secondAttribute="leadingMargin" id="Mo9-PS-he8"/>
|
||||
<constraint firstItem="ALQ-RY-TSP" firstAttribute="top" secondItem="j9d-rb-zxM" secondAttribute="topMargin" id="MuU-bI-eNQ"/>
|
||||
<constraint firstItem="ALQ-RY-TSP" firstAttribute="leading" secondItem="j9d-rb-zxM" secondAttribute="leadingMargin" id="QkX-MO-Cqk"/>
|
||||
<constraint firstItem="aFy-vJ-c96" firstAttribute="bottom" secondItem="j9d-rb-zxM" secondAttribute="bottomMargin" id="Uly-TV-NY5"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="aFy-vJ-c96" secondAttribute="trailing" id="jh2-Cp-hMN"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="aFy-vJ-c96" secondAttribute="bottom" id="ycJ-OY-NQ1"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="9u5-mU-FQu"/>
|
||||
<exclude reference="ycJ-OY-NQ1"/>
|
||||
</mask>
|
||||
</variation>
|
||||
<connections>
|
||||
<outlet property="imageView" destination="ALQ-RY-TSP" id="aFT-2a-amY"/>
|
||||
<outlet property="progressView" destination="aFy-vJ-c96" id="7IU-Af-fBh"/>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="s5Z-Tx-3S8" id="8LD-lB-cJh"/>
|
||||
<outlet property="delegate" destination="s5Z-Tx-3S8" id="dKK-9z-6UK"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<toolbarItems/>
|
||||
<navigationItem key="navigationItem" id="QjC-7y-Rne"/>
|
||||
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="collectionView" destination="Cs4-xi-Dza" id="0eB-76-G7I"/>
|
||||
</connections>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kxf-aO-E8S" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1980" y="183.5"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="dDd-lK-VlZ">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" navigationBarHidden="YES" toolbarHidden="NO" id="eT1-6D-OLs" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="j4N-h3-5Xi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="5Og-7f-jeE">
|
||||
<rect key="frame" x="0.0" y="556" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</toolbar>
|
||||
<connections>
|
||||
<segue destination="Mso-Eu-4PO" kind="relationship" relationship="rootViewController" id="j9T-my-S0p"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7rk-35-Omp" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="435" y="184"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
21
PhotoProgress/PhotoProgress/Images.xcassets/PhotoPlaceholder.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "PhotoPlaceholder.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
PhotoProgress/PhotoProgress/Images.xcassets/PhotoPlaceholder.imageset/PhotoPlaceholder.png
vendored
Normal file
After Width: | Height: | Size: 49 KiB |
|
@ -0,0 +1,48 @@
|
|||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.example.apple-sampleocode.PhotoProgress</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
Photo represents an image that can be imported.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class Photo: NSObject {
|
||||
// MARK: Properties
|
||||
|
||||
let imageURL: NSURL
|
||||
|
||||
/// Marked "dynamic" so it is KVO observable.
|
||||
dynamic var image: UIImage?
|
||||
|
||||
/// The photoImport is KVO observable for its progress.
|
||||
dynamic var photoImport: PhotoImport?
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(URL: NSURL) {
|
||||
imageURL = URL.copy() as! NSURL
|
||||
|
||||
image = UIImage(named: "PhotoPlaceholder")
|
||||
}
|
||||
|
||||
/// Kick off the import
|
||||
func startImport() -> NSProgress {
|
||||
let newImport = PhotoImport(URL: imageURL)
|
||||
|
||||
newImport.completionHandler = { image, error in
|
||||
if let image = image {
|
||||
// The import is finished. Set our image to the result
|
||||
self.image = image
|
||||
}
|
||||
else {
|
||||
self.reportError(error!)
|
||||
}
|
||||
|
||||
self.photoImport = nil
|
||||
}
|
||||
|
||||
newImport.start()
|
||||
|
||||
photoImport = newImport
|
||||
|
||||
return newImport.progress
|
||||
}
|
||||
|
||||
private func reportError(error: NSError) {
|
||||
if error.domain != NSCocoaErrorDomain || error.code != NSUserCancelledError {
|
||||
print("Error importing photo: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Go back to the initial state.
|
||||
func reset() {
|
||||
image = UIImage(named: "PhotoPlaceholder")
|
||||
photoImport = nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotoCollectionViewCell is a UICollectionViewCell subclass that shows a Photo.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
/// The KVO context used for all `PhotoCollectionViewCell` instances.
|
||||
private var photoCollectionViewCellObservationContext = 0
|
||||
|
||||
class PhotoCollectionViewCell: UICollectionViewCell {
|
||||
// MARK: Properties
|
||||
|
||||
@IBOutlet var imageView: UIImageView!
|
||||
@IBOutlet var progressView: UIProgressView!
|
||||
|
||||
private let fractionCompletedKeyPath = "photoImport.progress.fractionCompleted"
|
||||
|
||||
private let imageKeyPath = "image"
|
||||
|
||||
var photo: Photo? {
|
||||
willSet {
|
||||
if let formerPhoto = photo {
|
||||
formerPhoto.removeObserver(self, forKeyPath: fractionCompletedKeyPath, context: &photoCollectionViewCellObservationContext)
|
||||
formerPhoto.removeObserver(self, forKeyPath: imageKeyPath, context: &photoCollectionViewCellObservationContext)
|
||||
}
|
||||
}
|
||||
|
||||
didSet {
|
||||
if let newPhoto = photo {
|
||||
newPhoto.addObserver(self, forKeyPath: fractionCompletedKeyPath, options: [], context: &photoCollectionViewCellObservationContext)
|
||||
newPhoto.addObserver(self, forKeyPath: imageKeyPath, options: [], context: &photoCollectionViewCellObservationContext)
|
||||
}
|
||||
|
||||
updateImageView()
|
||||
updateProgressView()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateProgressView() {
|
||||
if let photoImport = photo?.photoImport {
|
||||
let fraction = Float(photoImport.progress.fractionCompleted)
|
||||
progressView.progress = fraction
|
||||
|
||||
progressView.hidden = false
|
||||
}
|
||||
else {
|
||||
progressView.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
private func updateImageView() {
|
||||
UIView.transitionWithView(imageView, duration: 0.5, options: .TransitionCrossDissolve, animations: {
|
||||
self.imageView.image = self.photo?.image
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: Key-Value Observing
|
||||
|
||||
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject: AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
||||
guard context == &photoCollectionViewCellObservationContext else {
|
||||
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
|
||||
return
|
||||
}
|
||||
|
||||
NSOperationQueue.mainQueue().addOperationWithBlock {
|
||||
if keyPath == self.fractionCompletedKeyPath {
|
||||
self.updateProgressView()
|
||||
}
|
||||
else if keyPath == self.imageKeyPath {
|
||||
self.updateImageView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotoDownload supports NSProgressReporting and "downloads" a file URL.
|
||||
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class PhotoDownload: NSObject, NSProgressReporting {
|
||||
// MARK: Properties
|
||||
|
||||
/// The URL to be downloaded.
|
||||
let downloadURL: NSURL
|
||||
|
||||
/**
|
||||
The completionHandler is called once the download is finished with either
|
||||
the downloaded data, or an `NSError`.
|
||||
*/
|
||||
var completionHandler: ((data: NSData?, error: NSError?) -> Void)?
|
||||
|
||||
let progress: NSProgress
|
||||
|
||||
/// A class containing the fake parts of our download.
|
||||
private class DownloadState {
|
||||
/// The dispatch queue that all of our callbacks will be invoked on.
|
||||
var queue: dispatch_queue_t!
|
||||
|
||||
/// The timer that drives the "download".
|
||||
var downloadTimer: dispatch_source_t?
|
||||
|
||||
/// The error that our didFail callback should be called with.
|
||||
var downloadError: NSError?
|
||||
|
||||
/// Whether or not we're paused.
|
||||
var isPaused = false
|
||||
}
|
||||
|
||||
private var downloadState: DownloadState
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(URL: NSURL) {
|
||||
downloadURL = URL.copy() as! NSURL
|
||||
|
||||
downloadState = DownloadState()
|
||||
|
||||
progress = NSProgress()
|
||||
|
||||
/*
|
||||
The progress starts out as indeterminate, since we don't know how many
|
||||
bytes there are to download yet.
|
||||
*/
|
||||
progress.totalUnitCount = -1
|
||||
|
||||
/*
|
||||
Since our units are bytes, we use NSProgressKindFile so the NSProgress's
|
||||
localizedDescription and localizedAdditionalDescription return
|
||||
something nicer.
|
||||
*/
|
||||
progress.kind = NSProgressKindFile
|
||||
|
||||
// We say we're a file operation so the localized descriptions are a little nicer.
|
||||
progress.setUserInfoObject(NSProgressFileOperationKindDownloading, forKey: NSProgressFileOperationKindKey)
|
||||
}
|
||||
|
||||
/// Start the download. Can only be called once.
|
||||
func start() {
|
||||
assert(nil == downloadState.queue, "`downloadState.queue` must not be nil in \(__FUNCTION__).")
|
||||
|
||||
// Fake a download.
|
||||
downloadState.queue = dispatch_queue_create("download queue", nil)
|
||||
dispatch_async(downloadState.queue) {
|
||||
do {
|
||||
// Fetch the data
|
||||
let data = try NSData(contentsOfURL: self.downloadURL, options: [])
|
||||
|
||||
// Our parameters for the "download".
|
||||
|
||||
// Update every 0.5 seconds.
|
||||
let interval: Double = 0.5
|
||||
|
||||
// Bytes per second.
|
||||
let throughput: Double = 5000
|
||||
|
||||
// Create a timer
|
||||
let downloadTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.downloadState.queue)
|
||||
|
||||
self.downloadState.downloadTimer = downloadTimer
|
||||
|
||||
var downloadedBytes = 0
|
||||
|
||||
// Add a random delay to the start, to simulate latency.
|
||||
let randomMilliseconds = Int64(arc4random_uniform(500))
|
||||
|
||||
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)) - Int64(randomMilliseconds * Int64(NSEC_PER_MSEC)))
|
||||
|
||||
dispatch_source_set_timer(downloadTimer, delay, UInt64(interval * Double(NSEC_PER_SEC)), 0)
|
||||
|
||||
dispatch_source_set_event_handler(downloadTimer) {
|
||||
// Update the downloaded bytes.
|
||||
downloadedBytes += Int(throughput * interval)
|
||||
|
||||
if downloadedBytes >= data.length {
|
||||
// We've finished!
|
||||
dispatch_source_cancel(downloadTimer)
|
||||
return
|
||||
}
|
||||
|
||||
// Call out that we've "downloaded" new data.
|
||||
self.didDownloadData(data, numberOfBytes: downloadedBytes)
|
||||
}
|
||||
|
||||
dispatch_source_set_cancel_handler(downloadTimer) {
|
||||
if downloadedBytes >= data.length {
|
||||
// Call out that we finished "downloading" data.
|
||||
self.didFinishDownload(data)
|
||||
}
|
||||
else {
|
||||
// Call out that we finished "downloading" data.
|
||||
self.didFailDownloadWithError(self.downloadState.downloadError!)
|
||||
}
|
||||
|
||||
self.downloadState.downloadTimer = nil
|
||||
}
|
||||
|
||||
// Call out that we will begin to "download" data.
|
||||
self.willBeginDownload(data.length)
|
||||
|
||||
dispatch_resume(downloadTimer)
|
||||
}
|
||||
catch let error {
|
||||
// Call out that we failed to "download" data.
|
||||
self.didFailDownloadWithError(error as NSError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func failDownloadWithError(error: NSError) {
|
||||
guard let downloadTimer = downloadState.downloadTimer else { return }
|
||||
|
||||
dispatch_async(downloadState.queue) {
|
||||
/*
|
||||
Set the downloadError, then cancel. The timer's cancellation handler
|
||||
will invoke the fail callback with the error, if we haven't finished
|
||||
by then.
|
||||
*/
|
||||
self.downloadState.downloadError = error
|
||||
|
||||
// Resume the timer before cancelling it.
|
||||
if self.downloadState.isPaused {
|
||||
dispatch_resume(downloadTimer)
|
||||
}
|
||||
|
||||
dispatch_source_cancel(downloadTimer)
|
||||
}
|
||||
}
|
||||
|
||||
private func suspendDownload() {
|
||||
if let downloadTimer = downloadState.downloadTimer {
|
||||
dispatch_async(downloadState.queue) {
|
||||
// Do not suspend if we're already suspended, or if we're cancelled.
|
||||
guard !self.downloadState.isPaused && 0 == dispatch_source_testcancel(downloadTimer) else { return }
|
||||
|
||||
// Simply suspend the timer.
|
||||
self.downloadState.isPaused = true
|
||||
dispatch_suspend(downloadTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resumeDownload() {
|
||||
if let downloadTimer = downloadState.downloadTimer {
|
||||
dispatch_async(downloadState.queue) {
|
||||
// Only resume if we're suspended and we're not cancelled.
|
||||
guard self.downloadState.isPaused && 0 == dispatch_source_testcancel(downloadTimer) else { return }
|
||||
|
||||
// Simply resume the timer.
|
||||
dispatch_resume(downloadTimer)
|
||||
self.downloadState.isPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func callCompletionHandler(data data: NSData?, error: NSError?) {
|
||||
// Call the completion handler if we have one.
|
||||
completionHandler?(data:data, error: error)
|
||||
|
||||
// Break any retain cycles by setting it to nil.
|
||||
completionHandler = nil
|
||||
}
|
||||
|
||||
// Called when the "download" begins
|
||||
func willBeginDownload(downloadLength: Int) {
|
||||
progress.totalUnitCount = Int64(downloadLength)
|
||||
|
||||
progress.cancellable = true
|
||||
progress.cancellationHandler = {
|
||||
let error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||
self.failDownloadWithError(error)
|
||||
}
|
||||
|
||||
progress.pausable = true
|
||||
|
||||
progress.pausingHandler = {
|
||||
self.suspendDownload()
|
||||
}
|
||||
|
||||
progress.resumingHandler = {
|
||||
self.resumeDownload()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called periodically as the "download" occurs. data and numberOfBytes are
|
||||
aggregated values, and contain the entire download up to that point.
|
||||
*/
|
||||
func didDownloadData(data: NSData, numberOfBytes: Int) {
|
||||
progress.completedUnitCount = Int64(numberOfBytes)
|
||||
}
|
||||
|
||||
/// Called when the "download" is completed.
|
||||
func didFinishDownload(downloadedData: NSData) {
|
||||
progress.completedUnitCount = Int64(downloadedData.length)
|
||||
callCompletionHandler(data: downloadedData, error: nil)
|
||||
}
|
||||
|
||||
/// Called if an error occurs during the "download"
|
||||
func didFailDownloadWithError(error: NSError) {
|
||||
callCompletionHandler(data: nil, error: error)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotoFilter has a class function for returning a filtered image.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class PhotoFilter {
|
||||
/// This supports implicit progress composition.
|
||||
class func filteredImage(image: UIImage) -> UIImage? {
|
||||
/*
|
||||
Set up the progress. First thing! It's indeterminate since we don't
|
||||
have any information about how long this is going to take.
|
||||
|
||||
`NSProgress(totalUnitCount:)` convenience adds itself as a child to the currentProgress.
|
||||
*/
|
||||
let progress = NSProgress(totalUnitCount: -1)
|
||||
progress.cancellable = false
|
||||
progress.pausable = false
|
||||
|
||||
var outputImage: UIImage? = nil
|
||||
|
||||
if let filter = CIFilter(name: "CIPhotoEffectTransfer"), cgImage = image.CGImage {
|
||||
let ciImage = CIImage(CGImage: cgImage)
|
||||
|
||||
filter.setValue(ciImage, forKey: "inputImage")
|
||||
|
||||
let outputCIImage = filter.outputImage
|
||||
|
||||
let ciContext = CIContext(options: [:])
|
||||
|
||||
let outputCGImage = ciContext.createCGImage(outputCIImage, fromRect: outputCIImage.extent)
|
||||
|
||||
outputImage = UIImage(CGImage: outputCGImage)
|
||||
}
|
||||
|
||||
// We have finished.
|
||||
progress.completedUnitCount = 1
|
||||
progress.totalUnitCount = 1
|
||||
|
||||
return outputImage
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotoImport represents the import operation of a Photo. It combines both the PhotoDownload and PhotoFilter operations.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class PhotoImport: NSObject, NSProgressReporting {
|
||||
// MARK: Properties
|
||||
|
||||
var completionHandler: ((image: UIImage?, error: NSError?) -> Void)?
|
||||
|
||||
let progress: NSProgress
|
||||
|
||||
let download: PhotoDownload
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(URL: NSURL) {
|
||||
progress = NSProgress()
|
||||
/*
|
||||
This progress's children are weighted: The download takes up 90%
|
||||
and the filter takes the remaining portion.
|
||||
*/
|
||||
progress.totalUnitCount = 10
|
||||
|
||||
download = PhotoDownload(URL: URL)
|
||||
}
|
||||
|
||||
func start() {
|
||||
/*
|
||||
Use explicit composition to add the download's progress to ours,
|
||||
taking 9/10 units.
|
||||
*/
|
||||
progress.addChild(download.progress, withPendingUnitCount: 9)
|
||||
|
||||
download.completionHandler = { data, error in
|
||||
guard let imageData = data, image = UIImage(data: imageData) else {
|
||||
self.callCompletionHandler(image: nil, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Make self.progress the currentProgress. Since the filteredImage
|
||||
supports implicit progress reporting, it will add its progress
|
||||
to ours.
|
||||
*/
|
||||
self.progress.becomeCurrentWithPendingUnitCount(1)
|
||||
let filteredImage = PhotoFilter.filteredImage(image)
|
||||
self.progress.resignCurrent()
|
||||
|
||||
self.callCompletionHandler(image: filteredImage, error: nil)
|
||||
}
|
||||
|
||||
download.start()
|
||||
}
|
||||
|
||||
private func callCompletionHandler(image image: UIImage?, error: NSError?) {
|
||||
completionHandler?(image: image, error: error)
|
||||
completionHandler = nil
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotosCollectionViewController is a UICollectionViewController subclass that has a reference to the Album.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class PhotosCollectionViewController: UICollectionViewController {
|
||||
// MARK: Properties
|
||||
|
||||
var album: Album? {
|
||||
didSet {
|
||||
collectionView?.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewController
|
||||
|
||||
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return album?.photos.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
The cell that is returned must be retrieved from a call to
|
||||
`dequeueReusableCellWithReuseIdentifier(_:forIndexPath:)`.
|
||||
*/
|
||||
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Photo", forIndexPath: indexPath) as! PhotoCollectionViewCell
|
||||
|
||||
cell.photo = album?.photos[indexPath.row]
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
PhotosViewController is our root UIViewController which is responsible for showing the overall progress and a tool bar.
|
||||
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
/**
|
||||
The KVO context used for all `PhotosViewController` instances. This provides a stable
|
||||
address to use as the context parameter for the KVO observation methods.
|
||||
*/
|
||||
private var photosViewControllerObservationContext = 0
|
||||
|
||||
class PhotosViewController: UIViewController {
|
||||
// MARK: Properties
|
||||
|
||||
/// The album that the app is importing
|
||||
private var album: Album?
|
||||
|
||||
/// Keys that we observe on `overallProgress`.
|
||||
private let overalProgressObservedKeys = [
|
||||
"fractionCompleted",
|
||||
"completedUnitCount",
|
||||
"totalUnitCount",
|
||||
"cancelled",
|
||||
"paused"
|
||||
]
|
||||
|
||||
/// The overall progress for the import that is shown to the user
|
||||
private var overallProgress: NSProgress? {
|
||||
willSet {
|
||||
guard let formerProgress = overallProgress else { return }
|
||||
|
||||
for overalProgressObservedKey in overalProgressObservedKeys {
|
||||
formerProgress.removeObserver(self, forKeyPath: overalProgressObservedKey, context: &photosViewControllerObservationContext)
|
||||
}
|
||||
}
|
||||
|
||||
didSet {
|
||||
if let newProgress = overallProgress {
|
||||
for overalProgressObservedKey in overalProgressObservedKeys {
|
||||
newProgress.addObserver(self, forKeyPath: overalProgressObservedKey, options: [], context: &photosViewControllerObservationContext)
|
||||
}
|
||||
}
|
||||
|
||||
updateProgressView()
|
||||
updateToolbar()
|
||||
}
|
||||
}
|
||||
|
||||
private var progressViewIsHidden = true
|
||||
|
||||
private var overallProgressIsFinished: Bool {
|
||||
let completed = overallProgress!.completedUnitCount
|
||||
let total = overallProgress!.totalUnitCount
|
||||
|
||||
// An NSProgress is finished if it's not indeterminate, and the completedUnitCount > totalUnitCount.
|
||||
return (completed >= total && total > 0 && completed > 0) || (completed > 0 && total == 0)
|
||||
}
|
||||
|
||||
// MARK: IBOutlets
|
||||
|
||||
@IBOutlet var progressView: UIProgressView!
|
||||
@IBOutlet var progressContainerView: UIView!
|
||||
|
||||
@IBOutlet var startToolbarItem: UIBarButtonItem!
|
||||
@IBOutlet var cancelToolbarItem: UIBarButtonItem!
|
||||
@IBOutlet var pauseToolbarItem: UIBarButtonItem!
|
||||
@IBOutlet var resumeToolbarItem: UIBarButtonItem!
|
||||
@IBOutlet var resetToolbarItem: UIBarButtonItem!
|
||||
|
||||
// MARK: IBActions
|
||||
|
||||
@IBAction func startImport() {
|
||||
overallProgress = album?.importPhotos()
|
||||
}
|
||||
|
||||
@IBAction func cancelImport() {
|
||||
overallProgress?.cancel()
|
||||
}
|
||||
|
||||
@IBAction func pauseImport() {
|
||||
overallProgress?.pause()
|
||||
}
|
||||
|
||||
@IBAction func resumeImport() {
|
||||
overallProgress?.resume()
|
||||
}
|
||||
|
||||
@IBAction func resetImport() {
|
||||
album?.resetPhotos()
|
||||
overallProgress = nil
|
||||
}
|
||||
|
||||
// MARK: Key-Value Observing
|
||||
|
||||
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
||||
guard context == &photosViewControllerObservationContext else {
|
||||
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
|
||||
return
|
||||
}
|
||||
|
||||
NSOperationQueue.mainQueue().addOperationWithBlock {
|
||||
self.updateProgressView()
|
||||
self.updateToolbar()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Update UI
|
||||
|
||||
private func updateProgressView() {
|
||||
let shouldHide: Bool
|
||||
|
||||
if let overallProgress = self.overallProgress {
|
||||
shouldHide = overallProgressIsFinished || overallProgress.cancelled
|
||||
|
||||
progressView.progress = Float(overallProgress.fractionCompleted)
|
||||
}
|
||||
else {
|
||||
shouldHide = true
|
||||
}
|
||||
|
||||
if progressViewIsHidden != shouldHide {
|
||||
UIView.animateWithDuration(0.2) {
|
||||
self.progressContainerView.alpha = shouldHide ? 0.0 : 1.0
|
||||
}
|
||||
|
||||
progressViewIsHidden = shouldHide
|
||||
}
|
||||
}
|
||||
|
||||
private func updateToolbar() {
|
||||
var items = [UIBarButtonItem]()
|
||||
|
||||
if let overallProgress = overallProgress {
|
||||
if overallProgressIsFinished || overallProgress.cancelled {
|
||||
items.append(resetToolbarItem)
|
||||
}
|
||||
else {
|
||||
// The import is running.
|
||||
items.append(cancelToolbarItem)
|
||||
|
||||
if overallProgress.paused {
|
||||
items.append(resumeToolbarItem)
|
||||
}
|
||||
else {
|
||||
items.append(pauseToolbarItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
items.append(startToolbarItem)
|
||||
}
|
||||
|
||||
navigationController?.toolbar?.setItems(items, animated: true)
|
||||
}
|
||||
|
||||
// MARK: Nib Loading
|
||||
|
||||
override func awakeFromNib() {
|
||||
album = Album()
|
||||
}
|
||||
|
||||
// MARK: Segue Handling
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
if segue.identifier == "collectionView" {
|
||||
if let collectionViewController = segue.destinationViewController as? PhotosCollectionViewController {
|
||||
collectionViewController.album = album
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
# PhotoProgress: Using NSProgress
|
||||
|
||||
This sample demonstrates how to create and compose NSProgress objects, and show their progress in your app.
|
||||
|
||||
The app presents a collection view of photos, which will initially be placeholder images. Tap the "Import" button to import the album of photos, showing progress for each individual import as well as an overall progress. The import operation for each photo is composed of a faked "download" step followed by a filter step. Once the import of a photo finishes, the final image is displayed in the collection view cell.
|
||||
|
||||
1) Album: Model object that represents an array of Photos. Loads Photos from the mainBundle's resources.
|
||||
|
||||
2) Photo: Model object that represents an image that can be imported. Has a method startImport which creates a PhotoImport.
|
||||
|
||||
3) PhotoImport: Single use object that composes the PhotoDownload and PhotoFilter operations. Conforms to NSProgressReporting.
|
||||
|
||||
4) PhotoDownload: Single use object that fakes a download of a fileURL. Conforms to NSProgressReporting.
|
||||
|
||||
5) PhotoFilter: Has a single class method, filteredImage, which synchronously runs a filter using CoreImage on a UIImage. Supports implicit progress reporting.
|
||||
|
||||
6) PhotosViewController: Our root view controller. Observes the overall progress using key-value observing and updates a UIProgressView. Handles interactions with the tool bar buttons.
|
||||
|
||||
7) PhotosCollectionViewController: Our UICollectionViewController. Has an Album which it uses in the data source methods.
|
||||
|
||||
8) PhotoCollectionViewCell: A UICollectionViewCell subclass that shows an individual Photo as well as progress for the import of that photo. Uses KVO to observe the progress of the import, as well as the image for the photo.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Build
|
||||
|
||||
Xcode 7.0, iOS 9.0 SDK
|
||||
|
||||
### Runtime
|
||||
|
||||
iOS 9.0
|
||||
|
||||
Copyright (C) 2015 Apple Inc. All rights reserved.
|