diff --git a/Design/Login.sketch b/Design/Login.sketch index be5c685..98d1014 100644 Binary files a/Design/Login.sketch and b/Design/Login.sketch differ diff --git a/README.md b/README.md index ccc5a8f..f9d88ae 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# iOS Animation Sample -This app is an iOS Animation playground to exercise different iOS Animations. The app is written in Swift. +iOS Animation Sample +============ + +This app is an iOS Animation playground to exercise different iOS Animations. The app is written in Swift 2. + +## Version +The current version is working with Xcode Version 7. If you are using different Xcode version, please check out the previous commits. + +## Features +* UIView Animations +* Auto Layout Animations + + + +## Screenshots +#### Animated Login screen using Auto Layout +![Login Animation](https://raw.githubusercontent.com/JakeLin/iOSAnimationSample/master/Screenshots/LoginAnimation.gif) + + + + +## TODO +* CALayer Animations +* Facebook Pop Animations diff --git a/Screenshots/LoginAnimation.gif b/Screenshots/LoginAnimation.gif new file mode 100644 index 0000000..b4ede00 Binary files /dev/null and b/Screenshots/LoginAnimation.gif differ diff --git a/iOSAnimationSample.xcodeproj/project.pbxproj b/iOSAnimationSample.xcodeproj/project.pbxproj index 86db813..b820600 100644 --- a/iOSAnimationSample.xcodeproj/project.pbxproj +++ b/iOSAnimationSample.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ AE337B3E1AF26525005F4AE1 /* ColorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE337B3D1AF26525005F4AE1 /* ColorViewController.swift */; }; AE337B401AF2E1F0005F4AE1 /* RotationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE337B3F1AF2E1F0005F4AE1 /* RotationViewController.swift */; }; AE6660241B0F1F5800C0B75C /* KeyframeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE6660231B0F1F5800C0B75C /* KeyframeViewController.swift */; }; + AE726BC31BC7EF0400D8060A /* AutoLayoutLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE726BC21BC7EF0400D8060A /* AutoLayoutLoginViewController.swift */; }; + AE726BC61BC7F02E00D8060A /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE726BC41BC7F02E00D8060A /* Login.storyboard */; }; AE80A0C61B08C9E70013F169 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE80A0C51B08C9E70013F169 /* LoginViewController.swift */; }; AE8A3A591AF2598200815F4B /* OpacityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8A3A581AF2598200815F4B /* OpacityViewController.swift */; }; AECE40101AEF7274005A5415 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECE400F1AEF7274005A5415 /* AppDelegate.swift */; }; @@ -43,6 +45,8 @@ AE337B3D1AF26525005F4AE1 /* ColorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorViewController.swift; sourceTree = ""; }; AE337B3F1AF2E1F0005F4AE1 /* RotationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotationViewController.swift; sourceTree = ""; }; AE6660231B0F1F5800C0B75C /* KeyframeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyframeViewController.swift; sourceTree = ""; }; + AE726BC21BC7EF0400D8060A /* AutoLayoutLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoLayoutLoginViewController.swift; sourceTree = ""; }; + AE726BC51BC7F02E00D8060A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Login.storyboard; sourceTree = ""; }; AE80A0C51B08C9E70013F169 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; AE8A3A581AF2598200815F4B /* OpacityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpacityViewController.swift; sourceTree = ""; }; AECE400A1AEF7274005A5415 /* iOSAnimationSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSAnimationSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -109,7 +113,9 @@ AE17FD411AF8A84B0086BC39 /* SpringViewController.swift */, AE80A0C51B08C9E70013F169 /* LoginViewController.swift */, AE6660231B0F1F5800C0B75C /* KeyframeViewController.swift */, + AE726BC21BC7EF0400D8060A /* AutoLayoutLoginViewController.swift */, AECE40131AEF7274005A5415 /* Main.storyboard */, + AE726BC41BC7F02E00D8060A /* Login.storyboard */, AECE40161AEF7274005A5415 /* Images.xcassets */, AECE40181AEF7274005A5415 /* LaunchScreen.xib */, AECE400D1AEF7274005A5415 /* Supporting Files */, @@ -188,7 +194,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0620; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = JakeLin; TargetAttributes = { AECE40091AEF7274005A5415 = { @@ -224,6 +230,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + AE726BC61BC7F02E00D8060A /* Login.storyboard in Resources */, AECE40151AEF7274005A5415 /* Main.storyboard in Resources */, AECE401A1AEF7274005A5415 /* LaunchScreen.xib in Resources */, AECE40171AEF7274005A5415 /* Images.xcassets in Resources */, @@ -248,6 +255,7 @@ AE337B3C1AF260C0005F4AE1 /* ScaleViewController.swift in Sources */, AE17FD401AF824990086BC39 /* EasingViewController.swift in Sources */, AE17FD421AF8A84B0086BC39 /* SpringViewController.swift in Sources */, + AE726BC31BC7EF0400D8060A /* AutoLayoutLoginViewController.swift in Sources */, AECE40121AEF7274005A5415 /* ViewController.swift in Sources */, AE8A3A591AF2598200815F4B /* OpacityViewController.swift in Sources */, AE6660241B0F1F5800C0B75C /* KeyframeViewController.swift in Sources */, @@ -278,6 +286,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + AE726BC41BC7F02E00D8060A /* Login.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AE726BC51BC7F02E00D8060A /* Base */, + ); + name = Login.storyboard; + sourceTree = ""; + }; AECE40131AEF7274005A5415 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -317,6 +333,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -380,6 +397,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = iOSAnimationSample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "JakeLin.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -390,6 +408,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = iOSAnimationSample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "JakeLin.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -408,6 +427,7 @@ ); INFOPLIST_FILE = iOSAnimationSampleTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "JakeLin.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSAnimationSample.app/iOSAnimationSample"; }; @@ -423,6 +443,7 @@ ); INFOPLIST_FILE = iOSAnimationSampleTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "JakeLin.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSAnimationSample.app/iOSAnimationSample"; }; diff --git a/iOSAnimationSample/AutoLayoutLoginViewController.swift b/iOSAnimationSample/AutoLayoutLoginViewController.swift new file mode 100644 index 0000000..5a90ead --- /dev/null +++ b/iOSAnimationSample/AutoLayoutLoginViewController.swift @@ -0,0 +1,149 @@ +// +// AutoLayoutLoginViewController.swift +// iOSAnimationSample +// +// Created by Jake Lin on 10/9/15. +// Copyright © 2015 JakeLin. All rights reserved. +// + +import UIKit + +class AutoLayoutLoginViewController: UIViewController { + + //MARK: IB outlets + @IBOutlet var bubbleImageViewGroup1: [UIImageView]! + @IBOutlet var bubbleImageViewGroup2: [UIImageView]! + @IBOutlet var dotImageView: UIImageView! + @IBOutlet var logoImageView: UIImageView! + @IBOutlet var usernameTextField: UITextField! + @IBOutlet var passwordTextField: UITextField! + @IBOutlet var loginButton: UIButton! + @IBOutlet var warningMessageView: UIView! + + @IBOutlet var loginButtonTopMarginConstraint: NSLayoutConstraint! + @IBOutlet var loginButtonWidthConstraint: NSLayoutConstraint! + + // Customer UI + let spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge) + + override func viewDidLoad() { + super.viewDidLoad() + + // Setup all bubbles start transform + for bubble in bubbleImageViewGroup1 { + bubble.transform = CGAffineTransformMakeScale(0, 0) + } + + for bubble in bubbleImageViewGroup2 { + bubble.transform = CGAffineTransformMakeScale(0, 0) + } + + // Setup username and password textfields + let paddingViewForUsername = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: usernameTextField.frame.height)) + usernameTextField.leftView = paddingViewForUsername + usernameTextField.leftViewMode = .Always + + let userImageView = UIImageView(image: UIImage(named: "User")) + userImageView.frame.origin = CGPoint(x: 13, y: 10) + usernameTextField.addSubview(userImageView) + + let paddingViewForPassword = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: passwordTextField.frame.height)) + passwordTextField.leftView = paddingViewForPassword + passwordTextField.leftViewMode = .Always + + let passwordImageView = UIImageView(image: UIImage(named: "Key")) + passwordImageView.frame.origin = CGPoint(x: 12, y: 9) + passwordTextField.addSubview(passwordImageView) + + // Prepare the elements for animations + logoImageView.transform = CGAffineTransformMakeTranslation(-view.frame.width, 0) + dotImageView.transform = CGAffineTransformMakeTranslation(-view.frame.width, 0) + usernameTextField.transform = CGAffineTransformMakeTranslation(-view.frame.width, 0) + passwordTextField.transform = CGAffineTransformMakeTranslation(-view.frame.width, 0) + loginButton.transform = CGAffineTransformMakeTranslation(-view.frame.width, 0) + } + + //MARK: - IBActions + @IBAction func startAnimationDidTap(sender: AnyObject) { + animate() + } + + @IBAction func loginDidTap(sender: AnyObject) { + loginButton.addSubview(spinner) + spinner.frame.origin = CGPoint(x: 12, y: 12) + spinner.startAnimating() + + loginButtonTopMarginConstraint.constant = 120 + loginButtonWidthConstraint.constant -= 60 + UIView.animateWithDuration(0.3, delay: 0.2, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], + animations: { + self.view.layoutIfNeeded() + }, completion: { _ in + self.spinner.removeFromSuperview() + self.loginButtonWidthConstraint.constant += 60 + UIView.animateWithDuration(1.5, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0, options: [], + animations: { + self.view.layoutIfNeeded() + }, completion: nil + ) + + UIView.transitionWithView(self.warningMessageView, + duration: 0.3, + options: [.TransitionFlipFromTop, .CurveEaseOut], + animations: { + self.warningMessageView.hidden = false + }, completion: nil + ) + } + ) + } + + // MARK: - Private methods + private func animate() { + UIView.animateWithDuration(0.3, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: [], + animations: { + for bubble in self.bubbleImageViewGroup1 { + bubble.transform = CGAffineTransformIdentity + } + }, completion: nil + ) + + UIView.animateWithDuration(0.3, delay: 0.1, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: [], + animations: { + for bubble in self.bubbleImageViewGroup2 { + bubble.transform = CGAffineTransformIdentity + } + }, completion: nil + ) + + UIView.animateWithDuration(0.3, delay: 0.2, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], + animations: { + self.logoImageView.transform = CGAffineTransformIdentity + }, completion: nil + ) + + UIView.animateWithDuration(0.6, delay: 0.25, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [], + animations: { + self.dotImageView.transform = CGAffineTransformIdentity + }, completion: nil + ) + + UIView.animateWithDuration(0.3, delay: 0.3, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], + animations: { + self.usernameTextField.transform = CGAffineTransformIdentity + }, completion: nil + ) + + UIView.animateWithDuration(0.3, delay: 0.4, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], + animations: { + self.passwordTextField.transform = CGAffineTransformIdentity + }, completion: nil + ) + + UIView.animateWithDuration(0.3, delay: 0.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], + animations: { + self.loginButton.transform = CGAffineTransformIdentity + }, completion: nil + ) + } +} diff --git a/iOSAnimationSample/Base.lproj/Login.storyboard b/iOSAnimationSample/Base.lproj/Login.storyboard new file mode 100644 index 0000000..e845dff --- /dev/null +++ b/iOSAnimationSample/Base.lproj/Login.storyboard @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSAnimationSample/Base.lproj/Main.storyboard b/iOSAnimationSample/Base.lproj/Main.storyboard index e8a745a..cacaadc 100644 --- a/iOSAnimationSample/Base.lproj/Main.storyboard +++ b/iOSAnimationSample/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -11,200 +12,264 @@ + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + - + - + + + + + + + + + + + + + + + + + + + @@ -223,21 +288,26 @@ + + + + - + - + - + - + + @@ -253,26 +323,31 @@ + + + + - + - + - + - + - + - + + @@ -294,6 +369,7 @@ + @@ -308,21 +384,26 @@ + + + + - + - + - + - + + @@ -338,24 +419,29 @@ + + + + - + - - + + @@ -372,15 +458,20 @@ + + + + - + - + + @@ -396,26 +487,31 @@ + + + + - + - + - + - + - + - + + @@ -433,31 +529,36 @@ + + + + - + - + - + - + - + - + - + - + + @@ -476,26 +577,31 @@ + + + + - + - + - + - + - + - + + @@ -513,57 +619,61 @@ + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -599,22 +710,27 @@ - + + + + + - + - + + @@ -627,6 +743,16 @@ + + + + + + + + + + @@ -641,9 +767,4 @@ - - - - - diff --git a/iOSAnimationSample/Info.plist b/iOSAnimationSample/Info.plist index 8c36cfd..6905cc6 100644 --- a/iOSAnimationSample/Info.plist +++ b/iOSAnimationSample/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - JakeLin.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/iOSAnimationSample/ViewController.swift b/iOSAnimationSample/ViewController.swift index 8ec4e85..3e7af81 100644 --- a/iOSAnimationSample/ViewController.swift +++ b/iOSAnimationSample/ViewController.swift @@ -19,7 +19,5 @@ class ViewController: UIViewController { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - - + } - diff --git a/iOSAnimationSampleTests/Info.plist b/iOSAnimationSampleTests/Info.plist index f36b78b..ba72822 100644 --- a/iOSAnimationSampleTests/Info.plist +++ b/iOSAnimationSampleTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - JakeLin.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName