├── .gitignore ├── Pods ├── PKHUD │ ├── PKHUD │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ ├── cross.imageset │ │ │ │ ├── cross.pdf │ │ │ │ └── Contents.json │ │ │ ├── checkmark.imageset │ │ │ │ ├── checkmark.pdf │ │ │ │ └── Contents.json │ │ │ ├── progress_activity.imageset │ │ │ │ ├── progress.pdf │ │ │ │ └── Contents.json │ │ │ └── progress_circular.imageset │ │ │ │ ├── progress_circular.pdf │ │ │ │ └── Contents.json │ │ ├── PKHUDAnimating.swift │ │ ├── PKHUD.h │ │ ├── PKHUDRotatingImageView.swift │ │ ├── PKHUDWideBaseView.swift │ │ ├── PKHUDProgressView.swift │ │ ├── PKHUDAssets.swift │ │ ├── PKHUDTextView.swift │ │ ├── PKHUDSystemActivityIndicatorView.swift │ │ ├── WindowRootViewController.swift │ │ ├── FrameView.swift │ │ ├── PKHUDSuccessView.swift │ │ ├── PKHUDAnimation.swift │ │ ├── PKHUDSquareBaseView.swift │ │ ├── Window.swift │ │ ├── PKHUDErrorView.swift │ │ ├── HUD.swift │ │ └── PKHUD.swift │ ├── LICENSE │ └── README.md ├── Target Support Files │ ├── PKHUD │ │ ├── PKHUD.modulemap │ │ ├── PKHUD-dummy.m │ │ ├── PKHUD-prefix.pch │ │ ├── PKHUD-umbrella.h │ │ ├── PKHUD.xcconfig │ │ └── Info.plist │ ├── Alamofire │ │ ├── Alamofire.modulemap │ │ ├── Alamofire-dummy.m │ │ ├── Alamofire-prefix.pch │ │ ├── Alamofire-umbrella.h │ │ ├── Alamofire.xcconfig │ │ └── Info.plist │ ├── Pods-Friends │ │ ├── Pods-Friends.modulemap │ │ ├── Pods-Friends-dummy.m │ │ ├── Pods-Friends-umbrella.h │ │ ├── Pods-Friends.debug.xcconfig │ │ ├── Pods-Friends.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-Friends-acknowledgements.markdown │ │ ├── Pods-Friends-acknowledgements.plist │ │ ├── Pods-Friends-resources.sh │ │ └── Pods-Friends-frameworks.sh │ ├── Pods-FriendsTests │ │ ├── Pods-FriendsTests.modulemap │ │ ├── Pods-FriendsTests-dummy.m │ │ ├── Pods-FriendsTests-umbrella.h │ │ ├── Pods-FriendsTests.debug.xcconfig │ │ ├── Pods-FriendsTests.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-FriendsTests-acknowledgements.markdown │ │ ├── Pods-FriendsTests-acknowledgements.plist │ │ ├── Pods-FriendsTests-resources.sh │ │ └── Pods-FriendsTests-frameworks.sh │ └── Pods-TestFriendViewFramework │ │ ├── Pods-TestFriendViewFramework.modulemap │ │ ├── Pods-TestFriendViewFramework-dummy.m │ │ ├── Pods-TestFriendViewFramework-umbrella.h │ │ ├── Pods-TestFriendViewFramework.debug.xcconfig │ │ ├── Pods-TestFriendViewFramework.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-TestFriendViewFramework-acknowledgements.markdown │ │ ├── Pods-TestFriendViewFramework-acknowledgements.plist │ │ ├── Pods-TestFriendViewFramework-frameworks.sh │ │ └── Pods-TestFriendViewFramework-resources.sh ├── Manifest.lock └── Alamofire │ ├── LICENSE │ └── Source │ ├── DispatchQueue+Alamofire.swift │ ├── Notifications.swift │ ├── Result.swift │ └── Timeline.swift ├── FriendViewHelper.playground ├── contents.xcplayground ├── Contents.swift └── timeline.xctimeline ├── Friends ├── JSON.swift ├── Friend.swift ├── Result.swift ├── Alert.swift ├── FriendCellViewModel.swift ├── Bindable.swift ├── FriendTableViewCell.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── UIViewControllerExtension.swift ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── AppDelegate.swift ├── FriendsTableViewViewModel.swift ├── UpdateFriendViewModel.swift ├── AddFriendViewModel.swift ├── FriendViewController.swift ├── FriendsTableViewController.swift └── AppServerClient.swift ├── Friends.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Friends - AppStore Release.xcscheme │ ├── Friends - Developer Release.xcscheme │ └── Friends - Development.xcscheme ├── Friends.xcworkspace └── contents.xcworkspacedata ├── Podfile.lock ├── Podfile ├── README.md ├── FriendsTests ├── MockFriend.swift ├── AlertTests.swift ├── Info.plist ├── BindableTests.swift ├── FriendTests.swift ├── UpdateFriendViewModelTests.swift ├── AddFriendViewModelTests.swift └── FriendsTableViewViewModelTests.swift ├── TestFriendViewFramework ├── TestFriendViewFramework.h └── Info.plist ├── FriendsUITests ├── Info.plist └── FriendsUITests.swift └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.pbxuser 4 | xcuserdata 5 | *.xccheckout 6 | DerivedData 7 | *.ipa 8 | *.xcuserstate 9 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/cross.imageset/cross.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JussiSuojanen/friends/HEAD/Pods/PKHUD/PKHUD/Images.xcassets/cross.imageset/cross.pdf -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/PKHUD.modulemap: -------------------------------------------------------------------------------- 1 | framework module PKHUD { 2 | umbrella header "PKHUD-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/PKHUD-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_PKHUD : NSObject 3 | @end 4 | @implementation PodsDummy_PKHUD 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/checkmark.imageset/checkmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JussiSuojanen/friends/HEAD/Pods/PKHUD/PKHUD/Images.xcassets/checkmark.imageset/checkmark.pdf -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/progress_activity.imageset/progress.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JussiSuojanen/friends/HEAD/Pods/PKHUD/PKHUD/Images.xcassets/progress_activity.imageset/progress.pdf -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Friends { 2 | umbrella header "Pods-Friends-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Friends : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Friends 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/progress_circular.imageset/progress_circular.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JussiSuojanen/friends/HEAD/Pods/PKHUD/PKHUD/Images.xcassets/progress_circular.imageset/progress_circular.pdf -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_FriendsTests { 2 | umbrella header "Pods-FriendsTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /FriendViewHelper.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_FriendsTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_FriendsTests 5 | @end 6 | -------------------------------------------------------------------------------- /Friends/JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 04/02/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | typealias JSON = Dictionary 10 | -------------------------------------------------------------------------------- /Friends.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_TestFriendViewFramework { 2 | umbrella header "Pods-TestFriendViewFramework-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/cross.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cross.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_TestFriendViewFramework : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_TestFriendViewFramework 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/checkmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checkmark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/progress_activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "progress.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/PKHUD-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Friends.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Friends/Friend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // friend.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 09/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | struct Friend: Codable { 10 | let firstname: String 11 | let lastname: String 12 | let phonenumber: String 13 | let id: Int 14 | } 15 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Images.xcassets/progress_circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "progress_circular.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /Friends/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 04/02/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | enum Result { 10 | case success(payload: T) 11 | case failure(U?) 12 | } 13 | 14 | enum EmptyResult { 15 | case success 16 | case failure(U?) 17 | } 18 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.3.0) 3 | - PKHUD (5.0.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.0) 7 | - PKHUD (~> 5.0) 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: 856a113053a7bc9cbe5d6367a555d773fc5cfef7 11 | PKHUD: 3c001512c6e002091184197a8de7a20420786e3b 12 | 13 | PODFILE CHECKSUM: a7a62049709a2b0bf2ad6fcd91a42e9e4a04a647 14 | 15 | COCOAPODS: 1.4.0 16 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.3.0) 3 | - PKHUD (5.0.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.0) 7 | - PKHUD (~> 5.0) 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: 856a113053a7bc9cbe5d6367a555d773fc5cfef7 11 | PKHUD: 3c001512c6e002091184197a8de7a20420786e3b 12 | 13 | PODFILE CHECKSUM: a7a62049709a2b0bf2ad6fcd91a42e9e4a04a647 14 | 15 | COCOAPODS: 1.4.0 16 | -------------------------------------------------------------------------------- /FriendViewHelper.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import TestFriendViewFramework 4 | 5 | let bundle = Bundle(for: FriendsTableViewController.self) 6 | let storyboard = UIStoryboard(name: "Main", bundle: bundle) 7 | 8 | let friendsTableViewController = storyboard.instantiateInitialViewController()! 9 | 10 | PlaygroundPage.current.liveView = friendsTableViewController 11 | 12 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDAnimating.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDAnimatingContentView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 9/27/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | @objc public protocol PKHUDAnimating { 13 | 14 | func startAnimation() 15 | @objc optional func stopAnimation() 16 | } 17 | -------------------------------------------------------------------------------- /Friends/Alert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alert.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 09/01/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | struct AlertAction { 11 | let buttonTitle: String 12 | let handler: (() -> Void)? 13 | } 14 | 15 | struct SingleButtonAlert { 16 | let title: String 17 | let message: String? 18 | let action: AlertAction 19 | } 20 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '10.0' 3 | use_frameworks! 4 | 5 | target 'Friends' do 6 | pod 'Alamofire', '~> 4.0' 7 | pod 'PKHUD', '~> 5.0' 8 | end 9 | 10 | target 'FriendsTests' do 11 | pod 'Alamofire', '~> 4.0' 12 | pod 'PKHUD', '~> 5.0' 13 | end 14 | 15 | target 'TestFriendViewFramework' do 16 | pod 'Alamofire', '~> 4.0' 17 | pod 'PKHUD', '~> 5.0' 18 | end 19 | 20 | -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/PKHUD-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "PKHUD.h" 14 | 15 | FOUNDATION_EXPORT double PKHUDVersionNumber; 16 | FOUNDATION_EXPORT const unsigned char PKHUDVersionString[]; 17 | 18 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_FriendsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_FriendsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUD.h 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/17/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | @import UIKit; 11 | 12 | //! Project version number for PKHUD. 13 | FOUNDATION_EXPORT double PKHUDVersionNumber; 14 | 15 | //! Project version string for PKHUD. 16 | FOUNDATION_EXPORT const unsigned char PKHUDVersionString[]; 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_FriendsTestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_FriendsTestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_TestFriendViewFrameworkVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_TestFriendViewFrameworkVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/PKHUD.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PKHUD 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/PKHUD 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Friend application 2 | 3 | This is an mobile client application for [FriendService](http://swiftyjimmy.com/server-side-swift-setup-backend/). Friend app is an example on how to use MVVM in an iOS application written in Swift. It uses a simple restful service to create, read, update and delete information on the friendService. 4 | 5 | Run "pod install" in folder where Podfile is located and the open project in xcode using the Friends.xcworkspace. 6 | 7 | 8 | ## Documentation 9 | Follow the instructions on [SwiftyJimmy](http://swiftyjimmy.com/mvvm-with-swift-application-part1/) to get better understanding on the code. 10 | 11 | -------------------------------------------------------------------------------- /FriendViewHelper.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Friends/FriendCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendCellViewModel.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 11/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | protocol FriendCellViewModel { 10 | var friendItem: Friend { get } 11 | var fullName: String { get } 12 | var phonenumberText: String { get } 13 | } 14 | 15 | extension Friend: FriendCellViewModel { 16 | var friendItem: Friend { 17 | return self 18 | } 19 | var fullName: String { 20 | return firstname + " " + lastname 21 | } 22 | var phonenumberText: String { 23 | return phonenumber 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FriendsTests/MockFriend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockFriend.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 17/04/2017. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Friend { 12 | static func with(id: Int = 0, 13 | firstname: String = "Jimmy", 14 | lastname: String = "Swift", 15 | phonenumber: String = "0501234567" ) -> Friend 16 | { 17 | return Friend(firstname: firstname, 18 | lastname: lastname, 19 | phonenumber: phonenumber, 20 | id: id) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDRotatingImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDRotatingImageView.swift 3 | // PKHUD 4 | // 5 | // Created by Mark Koh on 1/14/16. 6 | // Copyright © 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | import QuartzCore 12 | 13 | /// PKHUDRotatingImageView provides a content view that rotates the supplied image automatically. 14 | open class PKHUDRotatingImageView: PKHUDSquareBaseView, PKHUDAnimating { 15 | 16 | public func startAnimation() { 17 | imageView.layer.add(PKHUDAnimation.continuousRotation, forKey: "progressAnimation") 18 | } 19 | 20 | public func stopAnimation() { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Friends/Bindable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bindable.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 07/01/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | class Bindable { 10 | typealias Listener = ((T) -> Void) 11 | var listener: Listener? 12 | 13 | var value: T { 14 | didSet { 15 | listener?(value) 16 | } 17 | } 18 | 19 | init(_ v: T) { 20 | self.value = v 21 | } 22 | 23 | func bind(_ listener: Listener?) { 24 | self.listener = listener 25 | } 26 | 27 | func bindAndFire(_ listener: Listener?) { 28 | self.listener = listener 29 | listener?(value) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /TestFriendViewFramework/TestFriendViewFramework.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestFriendViewFramework.h 3 | // TestFriendViewFramework 4 | // 5 | // Created by Jussi Suojanen on 27/01/2018. 6 | // Copyright © 2018 Jimmy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TestFriendViewFramework. 12 | FOUNDATION_EXPORT double TestFriendViewFrameworkVersionNumber; 13 | 14 | //! Project version string for TestFriendViewFramework. 15 | FOUNDATION_EXPORT const unsigned char TestFriendViewFrameworkVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | -------------------------------------------------------------------------------- /Friends/FriendTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendTableViewCell.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 11/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FriendTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var labelFullName: UILabel! 14 | @IBOutlet weak var labelPhoneNumber: UILabel! 15 | 16 | var viewModel: FriendCellViewModel? { 17 | didSet { 18 | bindViewModel() 19 | } 20 | } 21 | 22 | private func bindViewModel() { 23 | labelFullName?.text = viewModel?.fullName 24 | labelPhoneNumber?.text = viewModel?.phonenumberText 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /FriendsTests/AlertTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertTests.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 23/04/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class AlertTests: XCTestCase { 12 | 13 | func testAlert() { 14 | let expectAlertActionHandlerCall = expectation(description: "Alert action handler called") 15 | 16 | let alert = SingleButtonAlert( 17 | title: "", 18 | message: "", 19 | action: AlertAction(buttonTitle: "", handler: { 20 | expectAlertActionHandlerCall.fulfill() 21 | }) 22 | ) 23 | 24 | alert.action.handler!() 25 | 26 | waitForExpectations(timeout: 0.1, handler: nil) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /FriendsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FriendsUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Friends/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDWideBaseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDWideBaseView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/12/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDWideBaseView provides a wide base view, which you can subclass and add additional views to. 13 | open class PKHUDWideBaseView: UIView { 14 | 15 | static let defaultWideBaseViewFrame = CGRect(origin: CGPoint.zero, size: CGSize(width: 265.0, height: 90.0)) 16 | 17 | public init() { 18 | super.init(frame: PKHUDWideBaseView.defaultWideBaseViewFrame) 19 | } 20 | 21 | public override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | } 24 | 25 | public required init?(coder aDecoder: NSCoder) { 26 | super.init(coder: aDecoder) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /TestFriendViewFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/PKHUD/PKHUD.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "PKHUD" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDProgressVIew.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/12/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | import QuartzCore 12 | 13 | /// PKHUDProgressView provides an indeterminate progress view. 14 | open class PKHUDProgressView: PKHUDSquareBaseView, PKHUDAnimating { 15 | 16 | public init(title: String? = nil, subtitle: String? = nil) { 17 | super.init(image: PKHUDAssets.progressActivityImage, title: title, subtitle: subtitle) 18 | } 19 | 20 | public required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | } 23 | 24 | public func startAnimation() { 25 | imageView.layer.add(PKHUDAnimation.discreteRotation, forKey: "progressAnimation") 26 | } 27 | 28 | public func stopAnimation() { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Pods/Target Support Files/PKHUD/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 5.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.3.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Friends/UIViewControllerExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewControllerExtension.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 22/06/2017. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SingleButtonDialogPresenter { 12 | func presentSingleButtonDialog(alert: SingleButtonAlert) 13 | } 14 | 15 | extension SingleButtonDialogPresenter where Self: UIViewController { 16 | func presentSingleButtonDialog(alert: SingleButtonAlert) { 17 | let alertController = UIAlertController(title: alert.title, 18 | message: alert.message, 19 | preferredStyle: .alert) 20 | alertController.addAction(UIAlertAction(title: alert.action.buttonTitle, 21 | style: .default, 22 | handler: { _ in alert.action.handler?() })) 23 | self.present(alertController, animated: true, completion: nil) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jussi Suojanen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /FriendsTests/BindableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BindableTests.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 23/04/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class BindableTests: XCTestCase { 12 | 13 | func testBind() { 14 | let bindable = Bindable(false) 15 | 16 | let expectListenerCalled = expectation(description: "Listener is called") 17 | bindable.bind { value in 18 | XCTAssert(value == true, "testBind failed, should have been true") 19 | expectListenerCalled.fulfill() 20 | } 21 | 22 | bindable.value = true 23 | waitForExpectations(timeout: 0.1, handler: nil) 24 | } 25 | 26 | func testBindAndFire() { 27 | let bindable = Bindable(true) 28 | 29 | let expectListenerCalled = expectation(description: "Listener is called") 30 | bindable.bindAndFire { value in 31 | XCTAssert(value == true, "testBindAndFire failed, should have been true") 32 | expectListenerCalled.fulfill() 33 | } 34 | 35 | waitForExpectations(timeout: 0.1, handler: nil) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FriendsTests/FriendTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendTests.swift 3 | // FriendTests 4 | // 5 | // Created by Jussi Suojanen on 07/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FriendTests: XCTestCase { 12 | 13 | func testSuccessfulInit() { 14 | let testSuccessfulJSON: JSON = ["id": 1, 15 | "firstname": "Jimmy", 16 | "lastname": "Swifty", 17 | "phonenumber": "0501234567"] 18 | 19 | XCTAssertNotNil(Friend(json: testSuccessfulJSON)) 20 | } 21 | } 22 | 23 | // Mark: - extension Friend 24 | private extension Friend { 25 | init?(json: JSON) { 26 | guard let id = json["id"] as? Int, 27 | let firstname = json["firstname"] as? String, 28 | let lastname = json["lastname"] as? String, 29 | let phonenumber = json["phonenumber"] as? String else { 30 | return nil 31 | } 32 | self.id = id 33 | self.firstname = firstname 34 | self.lastname = lastname 35 | self.phonenumber = phonenumber 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pods/PKHUD/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDAssets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUD.Assets.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/18/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDAssets provides a set of default images, that can be supplied to the PKHUD's content views. 13 | open class PKHUDAssets: NSObject { 14 | 15 | open class var crossImage: UIImage { return PKHUDAssets.bundledImage(named: "cross") } 16 | open class var checkmarkImage: UIImage { return PKHUDAssets.bundledImage(named: "checkmark") } 17 | open class var progressActivityImage: UIImage { return PKHUDAssets.bundledImage(named: "progress_activity") } 18 | open class var progressCircularImage: UIImage { return PKHUDAssets.bundledImage(named: "progress_circular") } 19 | 20 | internal class func bundledImage(named name: String) -> UIImage { 21 | let bundle = Bundle(for: PKHUDAssets.self) 22 | let image = UIImage(named: name, in:bundle, compatibleWith:nil) 23 | if let image = image { 24 | return image 25 | } 26 | 27 | return UIImage() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDTextView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/12/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDTextView provides a wide, three line text view, which you can use to display information. 13 | open class PKHUDTextView: PKHUDWideBaseView { 14 | 15 | public init(text: String?) { 16 | super.init() 17 | commonInit(text) 18 | } 19 | 20 | public required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | commonInit("") 23 | } 24 | 25 | func commonInit(_ text: String?) { 26 | titleLabel.text = text 27 | addSubview(titleLabel) 28 | } 29 | 30 | open override func layoutSubviews() { 31 | super.layoutSubviews() 32 | 33 | let padding: CGFloat = 10.0 34 | titleLabel.frame = bounds.insetBy(dx: padding, dy: padding) 35 | } 36 | 37 | open let titleLabel: UILabel = { 38 | let label = UILabel() 39 | label.textAlignment = .center 40 | label.font = UIFont.boldSystemFont(ofSize: 17.0) 41 | label.textColor = UIColor.black.withAlphaComponent(0.85) 42 | label.adjustsFontSizeToFitWidth = true 43 | label.numberOfLines = 3 44 | return label 45 | }() 46 | } 47 | -------------------------------------------------------------------------------- /FriendsUITests/FriendsUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendsUITests.swift 3 | // FriendsUITests 4 | // 5 | // Created by Jussi Suojanen on 07/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FriendsUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Friends/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDSystemActivityIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDSystemActivityIndicatorView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/12/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDSystemActivityIndicatorView provides the system UIActivityIndicatorView as an alternative. 13 | public final class PKHUDSystemActivityIndicatorView: PKHUDSquareBaseView, PKHUDAnimating { 14 | 15 | public init() { 16 | super.init(frame: PKHUDSquareBaseView.defaultSquareBaseViewFrame) 17 | commonInit() 18 | } 19 | 20 | public override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | commonInit() 23 | } 24 | 25 | public required init?(coder aDecoder: NSCoder) { 26 | super.init(coder: aDecoder) 27 | commonInit() 28 | } 29 | 30 | func commonInit () { 31 | backgroundColor = UIColor.clear 32 | alpha = 0.8 33 | 34 | self.addSubview(activityIndicatorView) 35 | } 36 | 37 | public override func layoutSubviews() { 38 | super.layoutSubviews() 39 | activityIndicatorView.center = self.center 40 | } 41 | 42 | let activityIndicatorView: UIActivityIndicatorView = { 43 | let activity = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) 44 | activity.color = UIColor.black 45 | return activity 46 | }() 47 | 48 | public func startAnimation() { 49 | activityIndicatorView.startAnimating() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/WindowRootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUD.WindowRootViewController.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/18/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// Serves as a configuration relay controller, tapping into the main window's rootViewController settings. 13 | internal class WindowRootViewController: UIViewController { 14 | 15 | internal override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 16 | if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController { 17 | return rootViewController.supportedInterfaceOrientations 18 | } else { 19 | return UIInterfaceOrientationMask.portrait 20 | } 21 | } 22 | 23 | internal override var preferredStatusBarStyle: UIStatusBarStyle { 24 | return self.presentingViewController?.preferredStatusBarStyle ?? UIApplication.shared.statusBarStyle 25 | } 26 | 27 | internal override var prefersStatusBarHidden: Bool { 28 | return self.presentingViewController?.prefersStatusBarHidden ?? UIApplication.shared.isStatusBarHidden 29 | } 30 | 31 | internal override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 32 | if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController { 33 | return rootViewController.preferredStatusBarUpdateAnimation 34 | } else { 35 | return .none 36 | } 37 | } 38 | 39 | internal override var shouldAutorotate: Bool { 40 | if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController { 41 | return rootViewController.shouldAutorotate 42 | } else { 43 | return false 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Friends/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/FrameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUDView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/16/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// Provides the general look and feel of the PKHUD, into which the eventual content is inserted. 13 | internal class FrameView: UIVisualEffectView { 14 | 15 | internal init() { 16 | super.init(effect: UIBlurEffect(style: .light)) 17 | commonInit() 18 | } 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | commonInit() 23 | } 24 | 25 | fileprivate func commonInit() { 26 | backgroundColor = UIColor(white: 0.8, alpha: 0.36) 27 | layer.cornerRadius = 9.0 28 | layer.masksToBounds = true 29 | 30 | contentView.addSubview(self.content) 31 | 32 | let offset = 20.0 33 | 34 | let motionEffectsX = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) 35 | motionEffectsX.maximumRelativeValue = offset 36 | motionEffectsX.minimumRelativeValue = -offset 37 | 38 | let motionEffectsY = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) 39 | motionEffectsY.maximumRelativeValue = offset 40 | motionEffectsY.minimumRelativeValue = -offset 41 | 42 | let group = UIMotionEffectGroup() 43 | group.motionEffects = [motionEffectsX, motionEffectsY] 44 | 45 | addMotionEffect(group) 46 | } 47 | 48 | fileprivate var _content = UIView() 49 | internal var content: UIView { 50 | get { 51 | return _content 52 | } 53 | set { 54 | _content.removeFromSuperview() 55 | _content = newValue 56 | _content.alpha = 0.85 57 | _content.clipsToBounds = true 58 | _content.contentMode = .center 59 | frame.size = _content.bounds.size 60 | contentView.addSubview(_content) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDSuccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDCheckmarkView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 9/27/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDCheckmarkView provides an animated success (checkmark) view. 13 | open class PKHUDSuccessView: PKHUDSquareBaseView, PKHUDAnimating { 14 | 15 | var checkmarkShapeLayer: CAShapeLayer = { 16 | let checkmarkPath = UIBezierPath() 17 | checkmarkPath.move(to: CGPoint(x: 4.0, y: 27.0)) 18 | checkmarkPath.addLine(to: CGPoint(x: 34.0, y: 56.0)) 19 | checkmarkPath.addLine(to: CGPoint(x: 88.0, y: 0.0)) 20 | 21 | let layer = CAShapeLayer() 22 | layer.frame = CGRect(x: 3.0, y: 3.0, width: 88.0, height: 56.0) 23 | layer.path = checkmarkPath.cgPath 24 | layer.fillMode = kCAFillModeForwards 25 | layer.lineCap = kCALineCapRound 26 | layer.lineJoin = kCALineJoinRound 27 | layer.fillColor = nil 28 | layer.strokeColor = UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 1.0).cgColor 29 | layer.lineWidth = 6.0 30 | return layer 31 | }() 32 | 33 | public init(title: String? = nil, subtitle: String? = nil) { 34 | super.init(title: title, subtitle: subtitle) 35 | layer.addSublayer(checkmarkShapeLayer) 36 | checkmarkShapeLayer.position = layer.position 37 | } 38 | 39 | public required init?(coder aDecoder: NSCoder) { 40 | super.init(coder: aDecoder) 41 | layer.addSublayer(checkmarkShapeLayer) 42 | checkmarkShapeLayer.position = layer.position 43 | } 44 | 45 | open func startAnimation() { 46 | let checkmarkStrokeAnimation = CAKeyframeAnimation(keyPath:"strokeEnd") 47 | checkmarkStrokeAnimation.values = [0, 1] 48 | checkmarkStrokeAnimation.keyTimes = [0, 1] 49 | checkmarkStrokeAnimation.duration = 0.35 50 | 51 | checkmarkShapeLayer.add(checkmarkStrokeAnimation, forKey:"checkmarkStrokeAnim") 52 | } 53 | 54 | open func stopAnimation() { 55 | checkmarkShapeLayer.removeAnimation(forKey: "checkmarkStrokeAnimation") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Friends/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 07/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDAnimation.swift 3 | // PKHUD 4 | // 5 | // Created by Piergiuseppe Longo on 06/01/16. 6 | // Copyright © 2016 Piergiuseppe Longo, NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import Foundation 11 | import QuartzCore 12 | 13 | public final class PKHUDAnimation { 14 | 15 | public static let discreteRotation: CAAnimation = { 16 | let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z") 17 | animation.values = [ 18 | NSNumber(value: 0.0), 19 | NSNumber(value: 1.0 * .pi / 6.0), 20 | NSNumber(value: 2.0 * .pi / 6.0), 21 | NSNumber(value: 3.0 * .pi / 6.0), 22 | NSNumber(value: 4.0 * .pi / 6.0), 23 | NSNumber(value: 5.0 * .pi / 6.0), 24 | NSNumber(value: 6.0 * .pi / 6.0), 25 | NSNumber(value: 7.0 * .pi / 6.0), 26 | NSNumber(value: 8.0 * .pi / 6.0), 27 | NSNumber(value: 9.0 * .pi / 6.0), 28 | NSNumber(value: 10.0 * .pi / 6.0), 29 | NSNumber(value: 11.0 * .pi / 6.0), 30 | NSNumber(value: 2.0 * .pi) 31 | ] 32 | animation.keyTimes = [ 33 | NSNumber(value: 0.0), 34 | NSNumber(value: 1.0 / 12.0), 35 | NSNumber(value: 2.0 / 12.0), 36 | NSNumber(value: 3.0 / 12.0), 37 | NSNumber(value: 4.0 / 12.0), 38 | NSNumber(value: 5.0 / 12.0), 39 | NSNumber(value: 0.5), 40 | NSNumber(value: 7.0 / 12.0), 41 | NSNumber(value: 8.0 / 12.0), 42 | NSNumber(value: 9.0 / 12.0), 43 | NSNumber(value: 10.0 / 12.0), 44 | NSNumber(value: 11.0 / 12.0), 45 | NSNumber(value: 1.0) 46 | ] 47 | animation.duration = 1.2 48 | animation.calculationMode = "discrete" 49 | animation.repeatCount = Float(INT_MAX) 50 | return animation 51 | }() 52 | 53 | static let continuousRotation: CAAnimation = { 54 | let animation = CABasicAnimation(keyPath: "transform.rotation.z") 55 | animation.fromValue = 0 56 | animation.toValue = 2.0 * .pi 57 | animation.duration = 1.2 58 | animation.repeatCount = Float(INT_MAX) 59 | return animation 60 | }() 61 | } 62 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## PKHUD 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | Generated by CocoaPods - https://cocoapods.org 51 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## PKHUD 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | Generated by CocoaPods - https://cocoapods.org 51 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## PKHUD 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | Generated by CocoaPods - https://cocoapods.org 51 | -------------------------------------------------------------------------------- /FriendsTests/UpdateFriendViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateFriendViewModelTests.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 22/04/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UpdateFriendViewModelTests: XCTestCase { 12 | 13 | func testPatchFriendSuccess() { 14 | let appServerClient = MockAppServerClient() 15 | appServerClient.patchFriendResult = .success(payload: Friend.with()) 16 | 17 | let viewModel = UpdateFriendViewModel(friend: Friend.with(), appServerClient: appServerClient) 18 | 19 | let expectNavigateCall = expectation(description: "Navigate back is called") 20 | 21 | viewModel.navigateBack = { 22 | expectNavigateCall.fulfill() 23 | } 24 | 25 | viewModel.submitFriend() 26 | 27 | waitForExpectations(timeout: 0.1, handler: nil) 28 | } 29 | 30 | func testPatchFriendFailure() { 31 | let appServerClient = MockAppServerClient() 32 | appServerClient.patchFriendResult = .failure(AppServerClient.PatchFriendFailureReason.notFound) 33 | 34 | let viewModel = UpdateFriendViewModel(friend: Friend.with(), appServerClient: appServerClient) 35 | 36 | let expectErrorShown = expectation(description: "OnShowError is called") 37 | 38 | viewModel.onShowError = { error in 39 | expectErrorShown.fulfill() 40 | } 41 | 42 | viewModel.submitFriend() 43 | 44 | waitForExpectations(timeout: 0.1, handler: nil) 45 | } 46 | 47 | func testValidateInputSuccess() { 48 | let mockFriend = Friend.with() 49 | let appServerClient = MockAppServerClient() 50 | 51 | let viewModel = UpdateFriendViewModel(friend: mockFriend, appServerClient: appServerClient) 52 | 53 | let expectUpdateSubmitButtonStateCall = expectation(description: "updateSubmitButtonState is called") 54 | 55 | viewModel.updateSubmitButtonState = { state in 56 | XCTAssert(state == true, "testValidateInputData failed. Data should be valid") 57 | expectUpdateSubmitButtonStateCall.fulfill() 58 | } 59 | 60 | viewModel.firstname = mockFriend.firstname 61 | viewModel.lastname = mockFriend.lastname 62 | viewModel.phonenumber = mockFriend.phonenumber 63 | 64 | waitForExpectations(timeout: 0.1, handler: nil) 65 | } 66 | 67 | } 68 | 69 | private final class MockAppServerClient: AppServerClient { 70 | var patchFriendResult: AppServerClient.PatchFriendResult? 71 | 72 | override func patchFriend(firstname: String, lastname: String, phonenumber: String, id: Int, completion: @escaping PatchFriendCompletion) { 73 | completion(patchFriendResult!) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FriendsTests/AddFriendViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddFriendViewModelTests.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 22/04/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class AddFriendViewModelTests: XCTestCase { 12 | 13 | func testAddFriendSuccess() { 14 | let appServerClient = MockAppServerClient() 15 | appServerClient.postFriendResult = .success 16 | 17 | let viewModel = AddFriendViewModel(appServerClient: appServerClient) 18 | 19 | let mockFriend = Friend.with() 20 | viewModel.firstname = mockFriend.firstname 21 | viewModel.lastname = mockFriend.lastname 22 | viewModel.phonenumber = mockFriend.phonenumber 23 | 24 | let expectNavigateCall = expectation(description: "Navigate back is called") 25 | 26 | viewModel.navigateBack = { 27 | expectNavigateCall.fulfill() 28 | } 29 | 30 | viewModel.submitFriend() 31 | 32 | waitForExpectations(timeout: 0.1, handler: nil) 33 | } 34 | 35 | func testAddFriendFailure() { 36 | let appServerClient = MockAppServerClient() 37 | appServerClient.postFriendResult = .failure(AppServerClient.PostFriendFailureReason(rawValue: 401)) 38 | 39 | let viewModel = AddFriendViewModel(appServerClient: appServerClient) 40 | 41 | let mockFriend = Friend.with() 42 | viewModel.firstname = mockFriend.firstname 43 | viewModel.lastname = mockFriend.lastname 44 | viewModel.phonenumber = mockFriend.phonenumber 45 | 46 | let expectErrorShown = expectation(description: "OnShowError is called") 47 | 48 | viewModel.onShowError = { error in 49 | expectErrorShown.fulfill() 50 | } 51 | 52 | viewModel.submitFriend() 53 | 54 | waitForExpectations(timeout: 0.1, handler: nil) 55 | } 56 | 57 | func testValidateInputSuccess() { 58 | let mockFriend = Friend.with() 59 | let appServerClient = MockAppServerClient() 60 | 61 | let viewModel = AddFriendViewModel(appServerClient: appServerClient) 62 | 63 | let expectUpdateSubmitButtonStateCall = expectation(description: "updateSubmitButtonState is called") 64 | 65 | viewModel.updateSubmitButtonState = { state in 66 | XCTAssert(state == true, "testValidateInputData failed. Data should be valid") 67 | expectUpdateSubmitButtonStateCall.fulfill() 68 | } 69 | 70 | viewModel.firstname = mockFriend.firstname 71 | viewModel.lastname = mockFriend.lastname 72 | viewModel.phonenumber = mockFriend.phonenumber 73 | 74 | waitForExpectations(timeout: 0.1, handler: nil) 75 | } 76 | 77 | } 78 | 79 | private final class MockAppServerClient: AppServerClient { 80 | var postFriendResult: AppServerClient.PostFriendResult? 81 | 82 | override func postFriend(firstname: String, lastname: String, phonenumber: String, completion: @escaping AppServerClient.PostFriendCompletion) { 83 | completion(postFriendResult!) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDSquareBaseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDSquareBaseView.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/12/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDSquareBaseView provides a square view, which you can subclass and add additional views to. 13 | open class PKHUDSquareBaseView: UIView { 14 | 15 | static let defaultSquareBaseViewFrame = CGRect(origin: CGPoint.zero, size: CGSize(width: 156.0, height: 156.0)) 16 | 17 | public override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | } 20 | 21 | public required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | } 24 | 25 | public init(image: UIImage? = nil, title: String? = nil, subtitle: String? = nil) { 26 | super.init(frame: PKHUDSquareBaseView.defaultSquareBaseViewFrame) 27 | self.imageView.image = image 28 | titleLabel.text = title 29 | subtitleLabel.text = subtitle 30 | 31 | addSubview(imageView) 32 | addSubview(titleLabel) 33 | addSubview(subtitleLabel) 34 | } 35 | 36 | open let imageView: UIImageView = { 37 | let imageView = UIImageView() 38 | imageView.alpha = 0.85 39 | imageView.clipsToBounds = true 40 | imageView.contentMode = .center 41 | return imageView 42 | }() 43 | 44 | open let titleLabel: UILabel = { 45 | let label = UILabel() 46 | label.textAlignment = .center 47 | label.font = UIFont.boldSystemFont(ofSize: 17.0) 48 | label.textColor = UIColor.black.withAlphaComponent(0.85) 49 | label.adjustsFontSizeToFitWidth = true 50 | label.minimumScaleFactor = 0.25 51 | return label 52 | }() 53 | 54 | open let subtitleLabel: UILabel = { 55 | let label = UILabel() 56 | label.textAlignment = .center 57 | label.font = UIFont.systemFont(ofSize: 14.0) 58 | label.textColor = UIColor.black.withAlphaComponent(0.7) 59 | label.adjustsFontSizeToFitWidth = true 60 | label.numberOfLines = 2 61 | label.adjustsFontSizeToFitWidth = true 62 | label.minimumScaleFactor = 0.25 63 | return label 64 | }() 65 | 66 | open override func layoutSubviews() { 67 | super.layoutSubviews() 68 | 69 | let viewWidth = bounds.size.width 70 | let viewHeight = bounds.size.height 71 | 72 | let halfHeight = CGFloat(ceilf(CFloat(viewHeight / 2.0))) 73 | let quarterHeight = CGFloat(ceilf(CFloat(viewHeight / 4.0))) 74 | let threeQuarterHeight = CGFloat(ceilf(CFloat(viewHeight / 4.0 * 3.0))) 75 | 76 | titleLabel.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: viewWidth, height: quarterHeight)) 77 | imageView.frame = CGRect(origin: CGPoint(x:0.0, y:quarterHeight), size: CGSize(width: viewWidth, height: halfHeight)) 78 | subtitleLabel.frame = CGRect(origin: CGPoint(x:0.0, y:threeQuarterHeight), size: CGSize(width: viewWidth, height: quarterHeight)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUDWindow.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/16/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// The window used to display the PKHUD within. Placed atop the applications main window. 13 | internal class ContainerView: UIView { 14 | 15 | internal let frameView: FrameView 16 | internal init(frameView: FrameView = FrameView()) { 17 | self.frameView = frameView 18 | super.init(frame: CGRect.zero) 19 | commonInit() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | frameView = FrameView() 24 | super.init(coder: aDecoder) 25 | commonInit() 26 | } 27 | 28 | fileprivate func commonInit() { 29 | backgroundColor = UIColor.clear 30 | isHidden = true 31 | 32 | addSubview(backgroundView) 33 | addSubview(frameView) 34 | } 35 | 36 | internal override func layoutSubviews() { 37 | super.layoutSubviews() 38 | 39 | frameView.center = center 40 | backgroundView.frame = bounds 41 | } 42 | 43 | internal func showFrameView() { 44 | layer.removeAllAnimations() 45 | frameView.center = center 46 | frameView.alpha = 1.0 47 | isHidden = false 48 | } 49 | 50 | fileprivate var willHide = false 51 | 52 | internal func hideFrameView(animated anim: Bool, completion: ((Bool) -> Void)? = nil) { 53 | let finalize: (_ finished: Bool) -> Void = { finished in 54 | self.isHidden = true 55 | self.removeFromSuperview() 56 | self.willHide = false 57 | 58 | completion?(finished) 59 | } 60 | 61 | if isHidden { 62 | return 63 | } 64 | 65 | willHide = true 66 | 67 | if anim { 68 | UIView.animate(withDuration: 0.8, animations: { 69 | self.frameView.alpha = 0.0 70 | self.hideBackground(animated: false) 71 | }, completion: { _ in finalize(true) }) 72 | } else { 73 | self.frameView.alpha = 0.0 74 | finalize(true) 75 | } 76 | } 77 | 78 | fileprivate let backgroundView: UIView = { 79 | let view = UIView() 80 | view.backgroundColor = UIColor(white:0.0, alpha:0.25) 81 | view.alpha = 0.0 82 | return view 83 | }() 84 | 85 | internal func showBackground(animated anim: Bool) { 86 | if anim { 87 | UIView.animate(withDuration: 0.175, animations: { 88 | self.backgroundView.alpha = 1.0 89 | }) 90 | } else { 91 | backgroundView.alpha = 1.0 92 | } 93 | } 94 | 95 | internal func hideBackground(animated anim: Bool) { 96 | if anim { 97 | UIView.animate(withDuration: 0.65, animations: { 98 | self.backgroundView.alpha = 0.0 99 | }) 100 | } else { 101 | backgroundView.alpha = 0.0 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Friends/FriendsTableViewViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendsTableViewViewModel.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 11/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | class FriendsTableViewViewModel { 10 | 11 | enum FriendTableViewCellType { 12 | case normal(cellViewModel: FriendCellViewModel) 13 | case error(message: String) 14 | case empty 15 | } 16 | 17 | var onShowError: ((_ alert: SingleButtonAlert) -> Void)? 18 | let showLoadingHud: Bindable = Bindable(false) 19 | 20 | let friendCells = Bindable([FriendTableViewCellType]()) 21 | let appServerClient: AppServerClient 22 | 23 | init(appServerClient: AppServerClient = AppServerClient()) { 24 | self.appServerClient = appServerClient 25 | } 26 | 27 | func getFriends() { 28 | showLoadingHud.value = true 29 | appServerClient.getFriends(completion: { [weak self] result in 30 | self?.showLoadingHud.value = false 31 | switch result { 32 | case .success(let friends): 33 | guard friends.count > 0 else { 34 | self?.friendCells.value = [.empty] 35 | return 36 | } 37 | self?.friendCells.value = friends.compactMap { .normal(cellViewModel: $0 as FriendCellViewModel)} 38 | case .failure(let error): 39 | self?.friendCells.value = [.error(message: error?.getErrorMessage() ?? "Loading failed, check network connection")] 40 | } 41 | }) 42 | } 43 | 44 | func deleteFriend(at index: Int) { 45 | switch friendCells.value[index] { 46 | case .normal(let vm): 47 | appServerClient.deleteFriend(id: vm.friendItem.id) { [weak self] result in 48 | switch result { 49 | case .success: 50 | self?.getFriends() 51 | case .failure(let error): 52 | let okAlert = SingleButtonAlert( 53 | title: error?.getErrorMessage() ?? "Could not connect to server. Check your network and try again later.", 54 | message: "Could not remove \(vm.fullName).", 55 | action: AlertAction(buttonTitle: "OK", handler: { print("Ok pressed!") }) 56 | ) 57 | self?.onShowError?(okAlert) 58 | } 59 | } 60 | default: 61 | // nop 62 | break 63 | } 64 | } 65 | } 66 | 67 | // MARK: - AppServerClient.GetFriendsFailureReason 68 | fileprivate extension AppServerClient.GetFriendsFailureReason { 69 | func getErrorMessage() -> String? { 70 | switch self { 71 | case .unAuthorized: 72 | return "Please login to load your friends." 73 | case .notFound: 74 | return "Could not complete request, please try again." 75 | } 76 | } 77 | } 78 | 79 | // MARK: - AppServerClient.DeleteFriendFailureReason 80 | fileprivate extension AppServerClient.DeleteFriendFailureReason { 81 | func getErrorMessage() -> String? { 82 | switch self { 83 | case .unAuthorized: 84 | return "Please login to remove friends." 85 | case .notFound: 86 | return "Friend not found." 87 | } 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /Friends/UpdateFriendViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateFriendViewModel.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 08/02/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | final class UpdateFriendViewModel: FriendViewModel { 10 | var friend: Friend 11 | var title: String { 12 | return "Update Friend" 13 | } 14 | var firstname: String? { 15 | didSet { 16 | validateInput() 17 | } 18 | } 19 | var lastname: String? { 20 | didSet { 21 | validateInput() 22 | } 23 | } 24 | var phonenumber: String? { 25 | didSet { 26 | validateInput() 27 | } 28 | } 29 | 30 | var validInputData: Bool = false { 31 | didSet { 32 | if oldValue != validInputData { 33 | updateSubmitButtonState?(validInputData) 34 | } 35 | } 36 | } 37 | 38 | var updateSubmitButtonState: ((Bool) -> ())? 39 | var navigateBack: (() -> ())? 40 | var onShowError: ((_ alert: SingleButtonAlert) -> Void)? 41 | 42 | let showLoadingHud = Bindable(false) 43 | let appServerClient: AppServerClient 44 | 45 | init(friend: Friend, appServerClient: AppServerClient = AppServerClient()) { 46 | self.friend = friend 47 | self.firstname = friend.firstname 48 | self.lastname = friend.lastname 49 | self.phonenumber = friend.phonenumber 50 | self.appServerClient = appServerClient 51 | } 52 | 53 | func submitFriend() { 54 | guard let firstname = firstname, 55 | let lastname = lastname, 56 | let phonenumber = phonenumber else { 57 | return 58 | } 59 | 60 | updateSubmitButtonState?(false) 61 | showLoadingHud.value = true 62 | 63 | appServerClient.patchFriend(firstname: firstname, lastname: lastname, phonenumber: phonenumber, id: friend.id) { [weak self] result in 64 | 65 | self?.updateSubmitButtonState?(true) 66 | self?.showLoadingHud.value = false 67 | 68 | switch result { 69 | case .success(_): 70 | self?.navigateBack?() 71 | case .failure(let error): 72 | let okAlert = SingleButtonAlert( 73 | title: error?.getErrorMessage() ?? "Could not connect to server. Check your network and try again later.", 74 | message: "Failed to update information.", 75 | action: AlertAction(buttonTitle: "OK", handler: { print("Ok pressed!") }) 76 | ) 77 | self?.onShowError?(okAlert) 78 | } 79 | } 80 | } 81 | 82 | func validateInput() { 83 | let validData = [firstname, lastname, phonenumber].filter { 84 | ($0?.count ?? 0) < 1 85 | } 86 | validInputData = (validData.count == 0) ? true : false 87 | } 88 | } 89 | 90 | fileprivate extension AppServerClient.PatchFriendFailureReason { 91 | func getErrorMessage() -> String? { 92 | switch self { 93 | case .unAuthorized: 94 | return "Please login to update friends friends." 95 | case .notFound: 96 | return "Failed to update friend. Please try again." 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUDErrorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKHUDErrorAnimation.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 9/27/15. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// PKHUDErrorView provides an animated error (cross) view. 13 | open class PKHUDErrorView: PKHUDSquareBaseView, PKHUDAnimating { 14 | 15 | var dashOneLayer = PKHUDErrorView.generateDashLayer() 16 | var dashTwoLayer = PKHUDErrorView.generateDashLayer() 17 | 18 | class func generateDashLayer() -> CAShapeLayer { 19 | let dash = CAShapeLayer() 20 | dash.frame = CGRect(x: 0.0, y: 0.0, width: 88.0, height: 88.0) 21 | dash.path = { 22 | let path = UIBezierPath() 23 | path.move(to: CGPoint(x: 0.0, y: 44.0)) 24 | path.addLine(to: CGPoint(x: 88.0, y: 44.0)) 25 | return path.cgPath 26 | }() 27 | dash.lineCap = kCALineCapRound 28 | dash.lineJoin = kCALineJoinRound 29 | dash.fillColor = nil 30 | dash.strokeColor = UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 1.0).cgColor 31 | dash.lineWidth = 6 32 | dash.fillMode = kCAFillModeForwards 33 | return dash 34 | } 35 | 36 | public init(title: String? = nil, subtitle: String? = nil) { 37 | super.init(title: title, subtitle: subtitle) 38 | layer.addSublayer(dashOneLayer) 39 | layer.addSublayer(dashTwoLayer) 40 | dashOneLayer.position = layer.position 41 | dashTwoLayer.position = layer.position 42 | } 43 | 44 | public required init?(coder aDecoder: NSCoder) { 45 | super.init(coder: aDecoder) 46 | layer.addSublayer(dashOneLayer) 47 | layer.addSublayer(dashTwoLayer) 48 | dashOneLayer.position = layer.position 49 | dashTwoLayer.position = layer.position 50 | } 51 | 52 | func rotationAnimation(_ angle: CGFloat) -> CABasicAnimation { 53 | var animation: CABasicAnimation 54 | if #available(iOS 9.0, *) { 55 | let springAnimation = CASpringAnimation(keyPath:"transform.rotation.z") 56 | springAnimation.damping = 1.5 57 | springAnimation.mass = 0.22 58 | springAnimation.initialVelocity = 0.5 59 | animation = springAnimation 60 | } else { 61 | animation = CABasicAnimation(keyPath:"transform.rotation.z") 62 | } 63 | 64 | animation.fromValue = 0.0 65 | animation.toValue = angle * CGFloat(.pi / 180.0) 66 | animation.duration = 1.0 67 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 68 | return animation 69 | } 70 | 71 | public func startAnimation() { 72 | let dashOneAnimation = rotationAnimation(-45.0) 73 | let dashTwoAnimation = rotationAnimation(45.0) 74 | 75 | dashOneLayer.transform = CATransform3DMakeRotation(-45 * CGFloat(.pi / 180.0), 0.0, 0.0, 1.0) 76 | dashTwoLayer.transform = CATransform3DMakeRotation(45 * CGFloat(.pi / 180.0), 0.0, 0.0, 1.0) 77 | 78 | dashOneLayer.add(dashOneAnimation, forKey: "dashOneAnimation") 79 | dashTwoLayer.add(dashTwoAnimation, forKey: "dashTwoAnimation") 80 | } 81 | 82 | public func stopAnimation() { 83 | dashOneLayer.removeAnimation(forKey: "dashOneAnimation") 84 | dashTwoLayer.removeAnimation(forKey: "dashTwoAnimation") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Friends/AddFriendViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddFriendViewModel.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 06/01/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | protocol FriendViewModel { 10 | var title: String { get } 11 | var firstname: String? { get set } 12 | var lastname: String? { get set } 13 | var phonenumber: String? { get set } 14 | var showLoadingHud: Bindable { get } 15 | 16 | var updateSubmitButtonState: ((Bool) -> ())? { get set } 17 | var navigateBack: (() -> ())? { get set } 18 | var onShowError: ((_ alert: SingleButtonAlert) -> Void)? { get set } 19 | 20 | func submitFriend() 21 | } 22 | 23 | final class AddFriendViewModel: FriendViewModel { 24 | var title: String { 25 | return "Add Friend" 26 | } 27 | var firstname: String? { 28 | didSet { 29 | validateInput() 30 | } 31 | } 32 | var lastname: String? { 33 | didSet { 34 | validateInput() 35 | } 36 | } 37 | var phonenumber: String? { 38 | didSet { 39 | validateInput() 40 | } 41 | } 42 | var updateSubmitButtonState: ((Bool) -> ())? 43 | var navigateBack: (() -> ())? 44 | var onShowError: ((_ alert: SingleButtonAlert) -> Void)? 45 | 46 | let showLoadingHud: Bindable = Bindable(false) 47 | 48 | private let appServerClient: AppServerClient 49 | private var validInputData: Bool = false { 50 | didSet { 51 | if oldValue != validInputData { 52 | updateSubmitButtonState?(validInputData) 53 | } 54 | } 55 | } 56 | 57 | init(appServerClient: AppServerClient = AppServerClient()) { 58 | self.appServerClient = appServerClient 59 | } 60 | 61 | func submitFriend() { 62 | guard let firstname = firstname, 63 | let lastname = lastname, 64 | let phonenumber = phonenumber else { 65 | return 66 | } 67 | 68 | updateSubmitButtonState?(false) 69 | showLoadingHud.value = true 70 | 71 | appServerClient.postFriend(firstname: firstname, lastname: lastname, phonenumber: phonenumber) { [weak self] result in 72 | self?.showLoadingHud.value = false 73 | self?.updateSubmitButtonState?(true) 74 | switch result { 75 | case .success: 76 | self?.navigateBack?() 77 | case .failure(let error): 78 | let okAlert = SingleButtonAlert( 79 | title: error?.getErrorMessage() ?? "Could not connect to server. Check your network and try again later.", 80 | message: "Could not add \(firstname) \(lastname).", 81 | action: AlertAction(buttonTitle: "OK", handler: { print("Ok pressed!") }) 82 | ) 83 | self?.onShowError?(okAlert) 84 | } 85 | } 86 | } 87 | 88 | func validateInput() { 89 | let validData = [firstname, lastname, phonenumber].filter { 90 | ($0?.count ?? 0) < 1 91 | } 92 | validInputData = (validData.count == 0) ? true : false 93 | } 94 | } 95 | 96 | private extension AppServerClient.PostFriendFailureReason { 97 | func getErrorMessage() -> String? { 98 | switch self { 99 | case .unAuthorized: 100 | return "Please login to add friends friends." 101 | case .notFound: 102 | return "Failed to add friend. Please try again." 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all 58 | copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | License 68 | MIT 69 | Title 70 | PKHUD 71 | Type 72 | PSGroupSpecifier 73 | 74 | 75 | FooterText 76 | Generated by CocoaPods - https://cocoapods.org 77 | Title 78 | 79 | Type 80 | PSGroupSpecifier 81 | 82 | 83 | StringsTable 84 | Acknowledgements 85 | Title 86 | Acknowledgements 87 | 88 | 89 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all 58 | copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | License 68 | MIT 69 | Title 70 | PKHUD 71 | Type 72 | PSGroupSpecifier 73 | 74 | 75 | FooterText 76 | Generated by CocoaPods - https://cocoapods.org 77 | Title 78 | 79 | Type 80 | PSGroupSpecifier 81 | 82 | 83 | StringsTable 84 | Acknowledgements 85 | Title 86 | Acknowledgements 87 | 88 | 89 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2014 Philip Kluz (Philip.Kluz@gmail.com) 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all 58 | copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | License 68 | MIT 69 | Title 70 | PKHUD 71 | Type 72 | PSGroupSpecifier 73 | 74 | 75 | FooterText 76 | Generated by CocoaPods - https://cocoapods.org 77 | Title 78 | 79 | Type 80 | PSGroupSpecifier 81 | 82 | 83 | StringsTable 84 | Acknowledgements 85 | Title 86 | Acknowledgements 87 | 88 | 89 | -------------------------------------------------------------------------------- /FriendsTests/FriendsTableViewViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendsTableViewViewModelTests.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 17/04/2017. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FriendsTableViewViewModelTests: XCTestCase { 12 | 13 | // MARK: - getFriend 14 | func testNormalFriendCells() { 15 | let appServerClient = MockAppServerClient() 16 | appServerClient.getFriendsResult = .success(payload: [Friend.with()]) 17 | 18 | let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) 19 | viewModel.getFriends() 20 | 21 | guard case .some(.normal(_)) = viewModel.friendCells.value.first else { 22 | XCTFail() 23 | return 24 | } 25 | } 26 | 27 | func testEmptyFriendCells() { 28 | let appServerClient = MockAppServerClient() 29 | appServerClient.getFriendsResult = .success(payload: []) 30 | 31 | let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) 32 | viewModel.getFriends() 33 | 34 | guard case .some(.empty) = viewModel.friendCells.value.first else { 35 | XCTFail() 36 | return 37 | } 38 | } 39 | 40 | func testErrorFriendCells() { 41 | let appServerClient = MockAppServerClient() 42 | appServerClient.getFriendsResult = .failure(AppServerClient.GetFriendsFailureReason.notFound) 43 | 44 | let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) 45 | viewModel.getFriends() 46 | 47 | guard case .some(.error(_)) = viewModel.friendCells.value.first else { 48 | XCTFail() 49 | return 50 | } 51 | } 52 | 53 | // MARK: - Delete friend 54 | func testDeleteFriendSuccess() { 55 | let appServerClient = MockAppServerClient() 56 | appServerClient.deleteFriendResult = .success 57 | appServerClient.getFriendsResult = .success(payload: [Friend.with()]) 58 | 59 | let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) 60 | viewModel.getFriends() 61 | 62 | guard case .some(.normal(_)) = viewModel.friendCells.value.first else { 63 | XCTFail() 64 | return 65 | } 66 | 67 | appServerClient.getFriendsResult = .success(payload: []) 68 | viewModel.deleteFriend(at: 0) 69 | 70 | guard case .some(.empty) = viewModel.friendCells.value.first else { 71 | XCTFail() 72 | return 73 | } 74 | } 75 | 76 | func testDeleteFriendFailure() { 77 | let appServerClient = MockAppServerClient() 78 | appServerClient.deleteFriendResult = .failure(AppServerClient.DeleteFriendFailureReason.notFound) 79 | 80 | let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) 81 | viewModel.friendCells.value = [Friend.with()].compactMap { .normal(cellViewModel: $0 as FriendCellViewModel)} 82 | 83 | let expectErrorShown = expectation(description: "Error note is shown") 84 | viewModel.onShowError = { _ in 85 | expectErrorShown.fulfill() 86 | } 87 | 88 | viewModel.deleteFriend(at: 0) 89 | 90 | waitForExpectations(timeout: 0.1, handler: nil) 91 | } 92 | } 93 | 94 | private final class MockAppServerClient: AppServerClient { 95 | var getFriendsResult: AppServerClient.GetFriendsResult? 96 | var deleteFriendResult: AppServerClient.DeleteFriendResult? 97 | 98 | override func getFriends(completion: @escaping AppServerClient.GetFriendsCompletion) { 99 | completion(getFriendsResult!) 100 | } 101 | 102 | override func deleteFriend(id: Int, completion: @escaping AppServerClient.DeleteFriendCompletion) { 103 | completion(deleteFriendResult!) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to represent whether a request was successful or encountered an error. 28 | /// 29 | /// - success: The request and all post processing operations were successful resulting in the serialization of the 30 | /// provided associated value. 31 | /// 32 | /// - failure: The request encountered an error resulting in a failure. The associated values are the original data 33 | /// provided by the server as well as the error that caused the failure. 34 | public enum Result { 35 | case success(Value) 36 | case failure(Error) 37 | 38 | /// Returns `true` if the result is a success, `false` otherwise. 39 | public var isSuccess: Bool { 40 | switch self { 41 | case .success: 42 | return true 43 | case .failure: 44 | return false 45 | } 46 | } 47 | 48 | /// Returns `true` if the result is a failure, `false` otherwise. 49 | public var isFailure: Bool { 50 | return !isSuccess 51 | } 52 | 53 | /// Returns the associated value if the result is a success, `nil` otherwise. 54 | public var value: Value? { 55 | switch self { 56 | case .success(let value): 57 | return value 58 | case .failure: 59 | return nil 60 | } 61 | } 62 | 63 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 64 | public var error: Error? { 65 | switch self { 66 | case .success: 67 | return nil 68 | case .failure(let error): 69 | return error 70 | } 71 | } 72 | } 73 | 74 | // MARK: - CustomStringConvertible 75 | 76 | extension Result: CustomStringConvertible { 77 | /// The textual representation used when written to an output stream, which includes whether the result was a 78 | /// success or failure. 79 | public var description: String { 80 | switch self { 81 | case .success: 82 | return "SUCCESS" 83 | case .failure: 84 | return "FAILURE" 85 | } 86 | } 87 | } 88 | 89 | // MARK: - CustomDebugStringConvertible 90 | 91 | extension Result: CustomDebugStringConvertible { 92 | /// The debug textual representation used when written to an output stream, which includes whether the result was a 93 | /// success or failure in addition to the value or error. 94 | public var debugDescription: String { 95 | switch self { 96 | case .success(let value): 97 | return "SUCCESS: \(value)" 98 | case .failure(let error): 99 | return "FAILURE: \(error)" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" 63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" 64 | fi 65 | } 66 | 67 | # Strip invalid architectures 68 | strip_invalid_archs() { 69 | binary="$1" 70 | # Get architectures for current file 71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 72 | stripped="" 73 | for arch in $archs; do 74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 75 | # Strip non-valid architectures in-place 76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 77 | stripped="$stripped $arch" 78 | fi 79 | done 80 | if [[ "$stripped" ]]; then 81 | echo "Stripped $binary of architectures:$stripped" 82 | fi 83 | } 84 | 85 | 86 | if [[ "$CONFIGURATION" == "Debug" ]]; then 87 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 88 | install_framework "$BUILT_PRODUCTS_DIR/PKHUD/PKHUD.framework" 89 | fi 90 | if [[ "$CONFIGURATION" == "Release" ]]; then 91 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 92 | install_framework "$BUILT_PRODUCTS_DIR/PKHUD/PKHUD.framework" 93 | fi 94 | -------------------------------------------------------------------------------- /Friends/FriendViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendViewController.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 06/01/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PKHUD 11 | 12 | final class FriendViewController: UIViewController { 13 | @IBOutlet weak var textFieldFirstname: UITextField! { 14 | didSet { 15 | textFieldFirstname.delegate = self 16 | textFieldFirstname.addTarget(self, action: 17 | #selector(firstnameTextFieldDidChange), 18 | for: .editingChanged) 19 | } 20 | } 21 | @IBOutlet weak var textFieldLastname: UITextField! { 22 | didSet { 23 | textFieldLastname.delegate = self 24 | textFieldLastname.addTarget(self, action: 25 | #selector(lastnameTextFieldDidChange), 26 | for: .editingChanged) 27 | } 28 | } 29 | @IBOutlet weak var textFieldPhoneNumber: UITextField! { 30 | didSet { 31 | textFieldPhoneNumber.delegate = self 32 | textFieldPhoneNumber.addTarget(self, action: 33 | #selector(phoneNumberTextFieldDidChange), 34 | for: .editingChanged) 35 | } 36 | } 37 | 38 | @IBOutlet weak var buttonSubmit: UIButton! 39 | 40 | var updateFriends: (() -> Void)? 41 | var viewModel: FriendViewModel? 42 | 43 | fileprivate var activeTextField: UITextField? 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | bindViewModel() 48 | } 49 | 50 | @objc 51 | func firstnameTextFieldDidChange(textField: UITextField){ 52 | viewModel?.firstname = textField.text ?? "" 53 | } 54 | 55 | @objc 56 | func lastnameTextFieldDidChange(textField: UITextField){ 57 | viewModel?.lastname = textField.text ?? "" 58 | } 59 | 60 | @objc 61 | func phoneNumberTextFieldDidChange(textField: UITextField){ 62 | viewModel?.phonenumber = textField.text ?? "" 63 | } 64 | 65 | func bindViewModel() { 66 | title = viewModel?.title 67 | textFieldFirstname?.text = viewModel?.firstname ?? "" 68 | textFieldLastname?.text = viewModel?.lastname ?? "" 69 | textFieldPhoneNumber?.text = viewModel?.phonenumber ?? "" 70 | 71 | viewModel?.showLoadingHud.bind { [weak self] visible in 72 | if let `self` = self { 73 | PKHUD.sharedHUD.contentView = PKHUDSystemActivityIndicatorView() 74 | visible ? PKHUD.sharedHUD.show(onView: self.view) : PKHUD.sharedHUD.hide() 75 | } 76 | } 77 | 78 | viewModel?.updateSubmitButtonState = { [weak self] state in 79 | self?.buttonSubmit?.isEnabled = state 80 | } 81 | 82 | viewModel?.navigateBack = { [weak self] in 83 | self?.updateFriends?() 84 | let _ = self?.navigationController?.popViewController(animated: true) 85 | } 86 | 87 | viewModel?.onShowError = { [weak self] alert in 88 | self?.presentSingleButtonDialog(alert: alert) 89 | } 90 | } 91 | } 92 | 93 | // MARK: - Actions 94 | extension FriendViewController { 95 | @IBAction func rootViewTapped(_ sender: Any) { 96 | activeTextField?.resignFirstResponder() 97 | } 98 | @IBAction func submitButtonTapped(_ sender: Any) { 99 | viewModel?.submitFriend() 100 | } 101 | } 102 | 103 | // MARK: - UITextFieldDelegate 104 | extension FriendViewController: UITextFieldDelegate { 105 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 106 | textField.resignFirstResponder() 107 | return false 108 | } 109 | 110 | func textFieldDidBeginEditing(_ textField: UITextField) { 111 | activeTextField = textField 112 | } 113 | 114 | func textFieldDidEndEditing(_ textField: UITextField) { 115 | activeTextField = nil 116 | } 117 | } 118 | 119 | extension FriendViewController: SingleButtonDialogPresenter { } 120 | 121 | -------------------------------------------------------------------------------- /Friends/FriendsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FriendsTableViewController.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 07/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PKHUD 11 | 12 | public class FriendsTableViewController: UITableViewController { 13 | 14 | let viewModel: FriendsTableViewViewModel = FriendsTableViewViewModel() 15 | 16 | public override func viewDidLoad() { 17 | super.viewDidLoad() 18 | bindViewModel() 19 | viewModel.getFriends() 20 | } 21 | 22 | func bindViewModel() { 23 | viewModel.friendCells.bindAndFire() { [weak self] _ in 24 | self?.tableView?.reloadData() 25 | } 26 | 27 | viewModel.onShowError = { [weak self] alert in 28 | self?.presentSingleButtonDialog(alert: alert) 29 | } 30 | 31 | 32 | viewModel.showLoadingHud.bind() { [weak self] visible in 33 | if let `self` = self { 34 | PKHUD.sharedHUD.contentView = PKHUDSystemActivityIndicatorView() 35 | visible ? PKHUD.sharedHUD.show(onView: self.view) : PKHUD.sharedHUD.hide() 36 | } 37 | } 38 | } 39 | 40 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 41 | if segue.identifier == "friendsToAddFriend", 42 | let destinationViewController = segue.destination as? FriendViewController { 43 | destinationViewController.viewModel = AddFriendViewModel() 44 | destinationViewController.updateFriends = { [weak self] in 45 | self?.viewModel.getFriends() 46 | } 47 | } 48 | 49 | if segue.identifier == "friendToUpdateFriend", 50 | let destinationViewController = segue.destination as? FriendViewController, 51 | let indexPath = tableView.indexPathForSelectedRow { 52 | 53 | switch viewModel.friendCells.value[indexPath.row] { 54 | case .normal(let viewModel): 55 | destinationViewController.viewModel = UpdateFriendViewModel(friend:viewModel.friendItem) 56 | destinationViewController.updateFriends = { [weak self] in 57 | self?.viewModel.getFriends() 58 | } 59 | case .empty, .error: 60 | // nop 61 | break 62 | } 63 | } 64 | } 65 | } 66 | 67 | // MARK: - UITableViewDelegate 68 | extension FriendsTableViewController { 69 | 70 | public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return viewModel.friendCells.value.count 72 | } 73 | 74 | public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | 76 | switch viewModel.friendCells.value[indexPath.row] { 77 | case .normal(let viewModel): 78 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "friendCell") as? FriendTableViewCell else { 79 | return UITableViewCell() 80 | } 81 | 82 | cell.viewModel = viewModel 83 | return cell 84 | case .error(let message): 85 | let cell = UITableViewCell() 86 | cell.isUserInteractionEnabled = false 87 | cell.textLabel?.text = message 88 | return cell 89 | case .empty: 90 | let cell = UITableViewCell() 91 | cell.isUserInteractionEnabled = false 92 | cell.textLabel?.text = "No data available" 93 | return cell 94 | } 95 | } 96 | 97 | public override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 98 | return true 99 | } 100 | 101 | public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 102 | if editingStyle == .delete { 103 | viewModel.deleteFriend(at: indexPath.row) 104 | } 105 | } 106 | } 107 | 108 | extension FriendsTableViewController: SingleButtonDialogPresenter { } 109 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/HUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUD.swift 3 | // PKHUD 4 | // 5 | // Created by Eugene Tartakovsky on 29/01/16. 6 | // Copyright © 2016 Eugene Tartakovsky, NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | public enum HUDContentType { 13 | case success 14 | case error 15 | case progress 16 | case image(UIImage?) 17 | case rotatingImage(UIImage?) 18 | 19 | case labeledSuccess(title: String?, subtitle: String?) 20 | case labeledError(title: String?, subtitle: String?) 21 | case labeledProgress(title: String?, subtitle: String?) 22 | case labeledImage(image: UIImage?, title: String?, subtitle: String?) 23 | case labeledRotatingImage(image: UIImage?, title: String?, subtitle: String?) 24 | 25 | case label(String?) 26 | case systemActivity 27 | } 28 | 29 | public final class HUD { 30 | 31 | // MARK: Properties 32 | public static var dimsBackground: Bool { 33 | get { return PKHUD.sharedHUD.dimsBackground } 34 | set { PKHUD.sharedHUD.dimsBackground = newValue } 35 | } 36 | 37 | public static var allowsInteraction: Bool { 38 | get { return PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled } 39 | set { PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled = newValue } 40 | } 41 | 42 | public static var isVisible: Bool { return PKHUD.sharedHUD.isVisible } 43 | 44 | // MARK: Public methods, PKHUD based 45 | public static func show(_ content: HUDContentType, onView view: UIView? = nil) { 46 | PKHUD.sharedHUD.contentView = contentView(content) 47 | PKHUD.sharedHUD.show(onView: view) 48 | } 49 | 50 | public static func hide(_ completion: ((Bool) -> Void)? = nil) { 51 | PKHUD.sharedHUD.hide(animated: false, completion: completion) 52 | } 53 | 54 | public static func hide(animated: Bool, completion: ((Bool) -> Void)? = nil) { 55 | PKHUD.sharedHUD.hide(animated: animated, completion: completion) 56 | } 57 | 58 | public static func hide(afterDelay delay: TimeInterval, completion: ((Bool) -> Void)? = nil) { 59 | PKHUD.sharedHUD.hide(afterDelay: delay, completion: completion) 60 | } 61 | 62 | // MARK: Public methods, HUD based 63 | public static func flash(_ content: HUDContentType, onView view: UIView? = nil) { 64 | HUD.show(content, onView: view) 65 | HUD.hide(animated: true, completion: nil) 66 | } 67 | 68 | public static func flash(_ content: HUDContentType, onView view: UIView? = nil, delay: TimeInterval, completion: ((Bool) -> Void)? = nil) { 69 | HUD.show(content, onView: view) 70 | HUD.hide(afterDelay: delay, completion: completion) 71 | } 72 | 73 | // MARK: Private methods 74 | fileprivate static func contentView(_ content: HUDContentType) -> UIView { 75 | switch content { 76 | case .success: 77 | return PKHUDSuccessView() 78 | case .error: 79 | return PKHUDErrorView() 80 | case .progress: 81 | return PKHUDProgressView() 82 | case let .image(image): 83 | return PKHUDSquareBaseView(image: image) 84 | case let .rotatingImage(image): 85 | return PKHUDRotatingImageView(image: image) 86 | 87 | case let .labeledSuccess(title, subtitle): 88 | return PKHUDSuccessView(title: title, subtitle: subtitle) 89 | case let .labeledError(title, subtitle): 90 | return PKHUDErrorView(title: title, subtitle: subtitle) 91 | case let .labeledProgress(title, subtitle): 92 | return PKHUDProgressView(title: title, subtitle: subtitle) 93 | case let .labeledImage(image, title, subtitle): 94 | return PKHUDSquareBaseView(image: image, title: title, subtitle: subtitle) 95 | case let .labeledRotatingImage(image, title, subtitle): 96 | return PKHUDRotatingImageView(image: image, title: title, subtitle: subtitle) 97 | 98 | case let .label(text): 99 | return PKHUDTextView(text: text) 100 | case .systemActivity: 101 | return PKHUDSystemActivityIndicatorView() 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Friends.xcodeproj/xcshareddata/xcschemes/Friends - AppStore Release.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Friends.xcodeproj/xcshareddata/xcschemes/Friends - Developer Release.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Friends.xcodeproj/xcshareddata/xcschemes/Friends - Development.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 85 | 87 | 93 | 94 | 95 | 96 | 99 | 100 | 103 | 104 | 105 | 106 | 110 | 111 | 115 | 116 | 117 | 118 | 119 | 120 | 126 | 128 | 134 | 135 | 136 | 137 | 139 | 140 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TestFriendViewFramework/Pods-TestFriendViewFramework-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Friends/AppServerClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppServerClient.swift 3 | // Friends 4 | // 5 | // Created by Jussi Suojanen on 07/11/16. 6 | // Copyright © 2016 Jimmy. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | 11 | // MARK: - AppServerClient 12 | class AppServerClient { 13 | 14 | // MARK: - GetFriends 15 | enum GetFriendsFailureReason: Int, Error { 16 | case unAuthorized = 401 17 | case notFound = 404 18 | } 19 | 20 | typealias GetFriendsResult = Result<[Friend], GetFriendsFailureReason> 21 | typealias GetFriendsCompletion = (_ result: GetFriendsResult) -> Void 22 | 23 | func getFriends(completion: @escaping GetFriendsCompletion) { 24 | Alamofire.request("http://friendservice.herokuapp.com/listFriends") 25 | .validate() 26 | .responseJSON { response in 27 | switch response.result { 28 | case .success: 29 | do { 30 | guard let data = response.data else { 31 | completion(.failure(nil)) 32 | return 33 | } 34 | 35 | let friends = try JSONDecoder().decode([Friend].self, from: data) 36 | completion(.success(payload: friends)) 37 | } catch { 38 | completion(.failure(nil)) 39 | } 40 | case .failure(_): 41 | if let statusCode = response.response?.statusCode, 42 | let reason = GetFriendsFailureReason(rawValue: statusCode) { 43 | completion(.failure(reason)) 44 | } 45 | completion(.failure(nil)) 46 | } 47 | } 48 | } 49 | 50 | // MARK: - PostFriend 51 | enum PostFriendFailureReason: Int, Error { 52 | case unAuthorized = 401 53 | case notFound = 404 54 | } 55 | 56 | typealias PostFriendResult = EmptyResult 57 | typealias PostFriendCompletion = (_ result: PostFriendResult) -> Void 58 | 59 | func postFriend(firstname: String, lastname: String, phonenumber: String, completion: @escaping PostFriendCompletion) { 60 | let param = ["firstname": firstname, 61 | "lastname": lastname, 62 | "phonenumber": phonenumber] 63 | Alamofire.request("https://friendservice.herokuapp.com/addFriend", method: .post, parameters: param, encoding: JSONEncoding.default) 64 | .validate() 65 | .responseJSON { response in 66 | switch response.result { 67 | case .success: 68 | completion(.success) 69 | case .failure(_): 70 | if let statusCode = response.response?.statusCode, 71 | let reason = PostFriendFailureReason(rawValue: statusCode) { 72 | completion(.failure(reason)) 73 | } 74 | completion(.failure(nil)) 75 | } 76 | } 77 | } 78 | 79 | // MARK: - PatchFriend 80 | enum PatchFriendFailureReason: Int, Error { 81 | case unAuthorized = 401 82 | case notFound = 404 83 | } 84 | 85 | typealias PatchFriendResult = Result 86 | typealias PatchFriendCompletion = (_ result: PatchFriendResult) -> Void 87 | 88 | func patchFriend(firstname: String, lastname: String, phonenumber: String, id: Int, completion: @escaping PatchFriendCompletion) { 89 | let param = ["firstname": firstname, 90 | "lastname": lastname, 91 | "phonenumber": phonenumber] 92 | Alamofire.request("https://friendservice.herokuapp.com/editFriend/\(id)", method: .patch, parameters: param, encoding: JSONEncoding.default) 93 | .validate() 94 | .responseJSON { response in 95 | switch response.result { 96 | case .success: 97 | do { 98 | guard let data = response.data else { 99 | completion(.failure(nil)) 100 | return 101 | } 102 | 103 | let friend = try JSONDecoder().decode(Friend.self, from: data) 104 | completion(.success(payload: friend)) 105 | } catch { 106 | completion(.failure(nil)) 107 | } 108 | case .failure(_): 109 | if let statusCode = response.response?.statusCode, 110 | let reason = PatchFriendFailureReason(rawValue: statusCode) { 111 | completion(.failure(reason)) 112 | } 113 | completion(.failure(nil)) 114 | } 115 | } 116 | } 117 | 118 | // MARK: - DeleteFriend 119 | enum DeleteFriendFailureReason: Int, Error { 120 | case unAuthorized = 401 121 | case notFound = 404 122 | } 123 | 124 | typealias DeleteFriendResult = EmptyResult 125 | typealias DeleteFriendCompletion = (_ result: DeleteFriendResult) -> Void 126 | 127 | func deleteFriend(id: Int, completion: @escaping DeleteFriendCompletion) { 128 | Alamofire.request("https://friendservice.herokuapp.com/editFriend/\(id)", method: .delete, parameters: nil, encoding: JSONEncoding.default) 129 | .validate() 130 | .responseJSON { response in 131 | switch response.result { 132 | case .success: 133 | completion(.success) 134 | case .failure(_): 135 | if let statusCode = response.response?.statusCode, 136 | let reason = DeleteFriendFailureReason(rawValue: statusCode) { 137 | completion(.failure(reason)) 138 | } 139 | completion(.failure(nil)) 140 | } 141 | } 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /Pods/PKHUD/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pkluz/PKHUD.svg?branch=master)](https://travis-ci.org/pkluz/PKHUD) 2 | [![License](https://img.shields.io/cocoapods/l/PKHUD.svg?style=flat)](https://cocoapods.org/pods/PKHUD) 3 | [![Platform](https://img.shields.io/cocoapods/p/PKHUD.svg?style=flat)](http://cocoadocs.org/docsets/PKHUD/3.2.1/) 4 | [![CocoaPod](https://img.shields.io/cocoapods/v/PKHUD.svg?style=flat)](https://cocoapods.org/pods/PKHUD) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | 7 | ![PKHUD - Swift and easy](https://raw.githubusercontent.com/pkluz/PKHUD/master/README_hero.png) 8 | 9 | A **Swift** based reimplementation of the Apple HUD (Volume, Ringer, Rotation,…) **for iOS 8** and up. 10 | 11 | ## Features 12 | - Official iOS 8 blur effect via **UIVisualEffectsView**. 13 | - Proper **rotation support**. 14 | - Size / **Device agnostic**. 15 | - Works on top of presented view controllers, alerts,... 16 | - Comes with several *free* resources - Checkmark, Cross, Progress Indicator,… 17 | - …as well as **animated** ones. 18 | - Builds as an **iOS 8 framework**. 19 | 20 | ![PKHUD.gif](https://cloud.githubusercontent.com/assets/1275218/10124182/09f4c406-654f-11e5-9cab-0f2e6f470887.gif) 21 | 22 | ## Installation 23 | **The recommended way is to use CocoaPods.** 24 | 25 | ### CocoaPods 26 | 27 | To install PKHUD for Swift 2 using CocoaPods, include the following in your Podfile 28 | 29 | ```ruby 30 | pod 'PKHUD', '~> 3.0' 31 | ``` 32 | 33 | To install PKHUD for Swift 3.x using CocoaPods, include the following in your Podfile 34 | 35 | ```ruby 36 | pod 'PKHUD', '~> 4.0' 37 | ``` 38 | 39 | To install PKHUD for Swift 4.x, include the following in your Podfile 40 | 41 | ```ruby 42 | pod 'PKHUD', '~> 5.0' 43 | ``` 44 | 45 | ### Carthage 46 | 47 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 48 | 49 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 50 | 51 | ```bash 52 | $ brew update 53 | $ brew install carthage 54 | ``` 55 | 56 | To integrate PKHUD into your Xcode project using Carthage, specify it in your `Cartfile`: 57 | 58 | ```ogdl 59 | github "pkluz/PKHUD" ~> 4.0 60 | ``` 61 | 62 | Run `carthage update` to build the framework and drag the built `PKHUD.framework` into your Xcode project. 63 | 64 | ## How To 65 | 66 | After adding the framework to your project, you need to import the module 67 | ```swift 68 | import PKHUD 69 | ``` 70 | 71 | Now, you can proceed to show an arbitrary HUD (and have it automatically disappear a second later) like this: 72 | ```swift 73 | HUD.flash(.success, delay: 1.0) 74 | ``` 75 | 76 | _or_ with a completion handler: 77 | 78 | ```swift 79 | HUD.flash(.success, delay: 1.0) { finished in 80 | // Completion Handler 81 | } 82 | ``` 83 | 84 | alternatively, you can use the more verbose and flexible “plumbing” API: 85 | 86 | ```swift 87 | PKHUD.sharedHUD.contentView = PKHUDSuccessView() 88 | PKHUD.sharedHUD.show() 89 | PKHUD.sharedHUD.hide(afterDelay: 1.0) { success in 90 | // Completion Handler 91 | } 92 | ``` 93 | 94 | You can also hot-swap content views - this can prove useful if you want to display a progress HUD first and transform it into a success or error HUD after an asynchronous operation has finished. 95 | ```swift 96 | HUD.show(.progress) 97 | 98 | // Now some long running task starts... 99 | delay(2.0) { 100 | // ...and once it finishes we flash the HUD for a second. 101 | HUD.flash(.success, delay: 1.0) 102 | } 103 | ``` 104 | 105 | Please note that there are _multiple_ types of content views that ship with PKHUD. You can find them as separate files in the project folder as well as in the `ContentViews` group in Xcode. 106 | 107 | ## Communication _(Hat Tip AlamoFire)_ 108 | 109 | - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/pkhud). (Tag 'pkhud') 110 | - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/pkhud). 111 | - If you **found a bug**, open an issue. 112 | - If you **have a feature request**, open an issue. 113 | - If you **want to contribute**, submit a pull request. 114 | 115 | 116 | ## Customization 117 | 118 | There are two properties at your disposal to customize general behavior. 119 | 120 | - `PKHUD.sharedHUD.dimsBackground: Bool` defines whether the background is slightly dimmed when the HUD is shown. 121 | 122 | - `PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled: Bool` defines whether the underlying views respond to touches while the HUD is shown. 123 | 124 | Additionally you are free to create you own custom content views. They can descend from any `UIView` type or the predefined base classes `PKHUDSquareBaseView` and `PKHUDWideBaseView`. 125 | 126 | **Note**: It's neither possible to customize the general look and feel, nor do I plan to add that feature. You are free to provide any content views you wish but the blurring, corner radius and shading will remain the same. 127 | 128 | ## Credits 129 | 130 | PKHUD is owned and maintained by Philip Kluz. Other mantainers are: 131 | 132 | - Piergiuseppe Longo [twitter](https://twitter.com/pglongo) 133 | 134 | 135 | ## License 136 | 137 | The MIT License (MIT) 138 | 139 | Copyright (c) 2015 Philip Kluz (Philip.Kluz@gmail.com) 140 | 141 | Permission is hereby granted, free of charge, to any person obtaining a copy 142 | of this software and associated documentation files (the "Software"), to deal 143 | in the Software without restriction, including without limitation the rights 144 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 145 | copies of the Software, and to permit persons to whom the Software is 146 | furnished to do so, subject to the following conditions: 147 | 148 | The above copyright notice and this permission notice shall be included in all 149 | copies or substantial portions of the Software. 150 | 151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 152 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 153 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 154 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 155 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 156 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 157 | SOFTWARE. 158 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Friends/Pods-Friends-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # Used as a return value for each invocation of `strip_invalid_archs` function. 10 | STRIP_BINARY_RETVAL=0 11 | 12 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 13 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 14 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 15 | 16 | # Copies and strips a vendored framework 17 | install_framework() 18 | { 19 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 20 | local source="${BUILT_PRODUCTS_DIR}/$1" 21 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 22 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 23 | elif [ -r "$1" ]; then 24 | local source="$1" 25 | fi 26 | 27 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 28 | 29 | if [ -L "${source}" ]; then 30 | echo "Symlinked..." 31 | source="$(readlink "${source}")" 32 | fi 33 | 34 | # Use filter instead of exclude so missing patterns don't throw errors. 35 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 36 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 37 | 38 | local basename 39 | basename="$(basename -s .framework "$1")" 40 | binary="${destination}/${basename}.framework/${basename}" 41 | if ! [ -r "$binary" ]; then 42 | binary="${destination}/${basename}" 43 | fi 44 | 45 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 46 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 47 | strip_invalid_archs "$binary" 48 | fi 49 | 50 | # Resign the code if required by the build settings to avoid unstable apps 51 | code_sign_if_enabled "${destination}/$(basename "$1")" 52 | 53 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 54 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 55 | local swift_runtime_libs 56 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 57 | for lib in $swift_runtime_libs; do 58 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 59 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 60 | code_sign_if_enabled "${destination}/${lib}" 61 | done 62 | fi 63 | } 64 | 65 | # Copies and strips a vendored dSYM 66 | install_dsym() { 67 | local source="$1" 68 | if [ -r "$source" ]; then 69 | # Copy the dSYM into a the targets temp dir. 70 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 71 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 72 | 73 | local basename 74 | basename="$(basename -s .framework.dSYM "$source")" 75 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 76 | 77 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 78 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then 79 | strip_invalid_archs "$binary" 80 | fi 81 | 82 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 83 | # Move the stripped file into its final destination. 84 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 85 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 86 | else 87 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 88 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 89 | fi 90 | fi 91 | } 92 | 93 | # Signs a framework with the provided identity 94 | code_sign_if_enabled() { 95 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 96 | # Use the current code_sign_identitiy 97 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 98 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 99 | 100 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 101 | code_sign_cmd="$code_sign_cmd &" 102 | fi 103 | echo "$code_sign_cmd" 104 | eval "$code_sign_cmd" 105 | fi 106 | } 107 | 108 | # Strip invalid architectures 109 | strip_invalid_archs() { 110 | binary="$1" 111 | # Get architectures for current target binary 112 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 113 | # Intersect them with the architectures we are building for 114 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 115 | # If there are no archs supported by this binary then warn the user 116 | if [[ -z "$intersected_archs" ]]; then 117 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 118 | STRIP_BINARY_RETVAL=0 119 | return 120 | fi 121 | stripped="" 122 | for arch in $binary_archs; do 123 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 124 | # Strip non-valid architectures in-place 125 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 126 | stripped="$stripped $arch" 127 | fi 128 | done 129 | if [[ "$stripped" ]]; then 130 | echo "Stripped $binary of architectures:$stripped" 131 | fi 132 | STRIP_BINARY_RETVAL=1 133 | } 134 | 135 | 136 | if [[ "$CONFIGURATION" == "Debug" ]]; then 137 | install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" 138 | install_framework "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework" 139 | fi 140 | if [[ "$CONFIGURATION" == "Release" ]]; then 141 | install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" 142 | install_framework "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework" 143 | fi 144 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 145 | wait 146 | fi 147 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-FriendsTests/Pods-FriendsTests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # Used as a return value for each invocation of `strip_invalid_archs` function. 10 | STRIP_BINARY_RETVAL=0 11 | 12 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 13 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 14 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 15 | 16 | # Copies and strips a vendored framework 17 | install_framework() 18 | { 19 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 20 | local source="${BUILT_PRODUCTS_DIR}/$1" 21 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 22 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 23 | elif [ -r "$1" ]; then 24 | local source="$1" 25 | fi 26 | 27 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 28 | 29 | if [ -L "${source}" ]; then 30 | echo "Symlinked..." 31 | source="$(readlink "${source}")" 32 | fi 33 | 34 | # Use filter instead of exclude so missing patterns don't throw errors. 35 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 36 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 37 | 38 | local basename 39 | basename="$(basename -s .framework "$1")" 40 | binary="${destination}/${basename}.framework/${basename}" 41 | if ! [ -r "$binary" ]; then 42 | binary="${destination}/${basename}" 43 | fi 44 | 45 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 46 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 47 | strip_invalid_archs "$binary" 48 | fi 49 | 50 | # Resign the code if required by the build settings to avoid unstable apps 51 | code_sign_if_enabled "${destination}/$(basename "$1")" 52 | 53 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 54 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 55 | local swift_runtime_libs 56 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 57 | for lib in $swift_runtime_libs; do 58 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 59 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 60 | code_sign_if_enabled "${destination}/${lib}" 61 | done 62 | fi 63 | } 64 | 65 | # Copies and strips a vendored dSYM 66 | install_dsym() { 67 | local source="$1" 68 | if [ -r "$source" ]; then 69 | # Copy the dSYM into a the targets temp dir. 70 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 71 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 72 | 73 | local basename 74 | basename="$(basename -s .framework.dSYM "$source")" 75 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 76 | 77 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 78 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then 79 | strip_invalid_archs "$binary" 80 | fi 81 | 82 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 83 | # Move the stripped file into its final destination. 84 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 85 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 86 | else 87 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 88 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 89 | fi 90 | fi 91 | } 92 | 93 | # Signs a framework with the provided identity 94 | code_sign_if_enabled() { 95 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 96 | # Use the current code_sign_identitiy 97 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 98 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 99 | 100 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 101 | code_sign_cmd="$code_sign_cmd &" 102 | fi 103 | echo "$code_sign_cmd" 104 | eval "$code_sign_cmd" 105 | fi 106 | } 107 | 108 | # Strip invalid architectures 109 | strip_invalid_archs() { 110 | binary="$1" 111 | # Get architectures for current target binary 112 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 113 | # Intersect them with the architectures we are building for 114 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 115 | # If there are no archs supported by this binary then warn the user 116 | if [[ -z "$intersected_archs" ]]; then 117 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 118 | STRIP_BINARY_RETVAL=0 119 | return 120 | fi 121 | stripped="" 122 | for arch in $binary_archs; do 123 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 124 | # Strip non-valid architectures in-place 125 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 126 | stripped="$stripped $arch" 127 | fi 128 | done 129 | if [[ "$stripped" ]]; then 130 | echo "Stripped $binary of architectures:$stripped" 131 | fi 132 | STRIP_BINARY_RETVAL=1 133 | } 134 | 135 | 136 | if [[ "$CONFIGURATION" == "Debug" ]]; then 137 | install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" 138 | install_framework "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework" 139 | fi 140 | if [[ "$CONFIGURATION" == "Release" ]]; then 141 | install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" 142 | install_framework "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework" 143 | fi 144 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 145 | wait 146 | fi 147 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Timeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Responsible for computing the timing metrics for the complete lifecycle of a `Request`. 28 | public struct Timeline { 29 | /// The time the request was initialized. 30 | public let requestStartTime: CFAbsoluteTime 31 | 32 | /// The time the first bytes were received from or sent to the server. 33 | public let initialResponseTime: CFAbsoluteTime 34 | 35 | /// The time when the request was completed. 36 | public let requestCompletedTime: CFAbsoluteTime 37 | 38 | /// The time when the response serialization was completed. 39 | public let serializationCompletedTime: CFAbsoluteTime 40 | 41 | /// The time interval in seconds from the time the request started to the initial response from the server. 42 | public let latency: TimeInterval 43 | 44 | /// The time interval in seconds from the time the request started to the time the request completed. 45 | public let requestDuration: TimeInterval 46 | 47 | /// The time interval in seconds from the time the request completed to the time response serialization completed. 48 | public let serializationDuration: TimeInterval 49 | 50 | /// The time interval in seconds from the time the request started to the time response serialization completed. 51 | public let totalDuration: TimeInterval 52 | 53 | /// Creates a new `Timeline` instance with the specified request times. 54 | /// 55 | /// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`. 56 | /// - parameter initialResponseTime: The time the first bytes were received from or sent to the server. 57 | /// Defaults to `0.0`. 58 | /// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`. 59 | /// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults 60 | /// to `0.0`. 61 | /// 62 | /// - returns: The new `Timeline` instance. 63 | public init( 64 | requestStartTime: CFAbsoluteTime = 0.0, 65 | initialResponseTime: CFAbsoluteTime = 0.0, 66 | requestCompletedTime: CFAbsoluteTime = 0.0, 67 | serializationCompletedTime: CFAbsoluteTime = 0.0) 68 | { 69 | self.requestStartTime = requestStartTime 70 | self.initialResponseTime = initialResponseTime 71 | self.requestCompletedTime = requestCompletedTime 72 | self.serializationCompletedTime = serializationCompletedTime 73 | 74 | self.latency = initialResponseTime - requestStartTime 75 | self.requestDuration = requestCompletedTime - requestStartTime 76 | self.serializationDuration = serializationCompletedTime - requestCompletedTime 77 | self.totalDuration = serializationCompletedTime - requestStartTime 78 | } 79 | } 80 | 81 | // MARK: - CustomStringConvertible 82 | 83 | extension Timeline: CustomStringConvertible { 84 | /// The textual representation used when written to an output stream, which includes the latency, the request 85 | /// duration and the total duration. 86 | public var description: String { 87 | let latency = String(format: "%.3f", self.latency) 88 | let requestDuration = String(format: "%.3f", self.requestDuration) 89 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 90 | let totalDuration = String(format: "%.3f", self.totalDuration) 91 | 92 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 93 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 94 | let timings = [ 95 | "\"Latency\": " + latency + " secs", 96 | "\"Request Duration\": " + requestDuration + " secs", 97 | "\"Serialization Duration\": " + serializationDuration + " secs", 98 | "\"Total Duration\": " + totalDuration + " secs" 99 | ] 100 | 101 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 102 | } 103 | } 104 | 105 | // MARK: - CustomDebugStringConvertible 106 | 107 | extension Timeline: CustomDebugStringConvertible { 108 | /// The textual representation used when written to an output stream, which includes the request start time, the 109 | /// initial response time, the request completed time, the serialization completed time, the latency, the request 110 | /// duration and the total duration. 111 | public var debugDescription: String { 112 | let requestStartTime = String(format: "%.3f", self.requestStartTime) 113 | let initialResponseTime = String(format: "%.3f", self.initialResponseTime) 114 | let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime) 115 | let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime) 116 | let latency = String(format: "%.3f", self.latency) 117 | let requestDuration = String(format: "%.3f", self.requestDuration) 118 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 119 | let totalDuration = String(format: "%.3f", self.totalDuration) 120 | 121 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 122 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 123 | let timings = [ 124 | "\"Request Start Time\": " + requestStartTime, 125 | "\"Initial Response Time\": " + initialResponseTime, 126 | "\"Request Completed Time\": " + requestCompletedTime, 127 | "\"Serialization Completed Time\": " + serializationCompletedTime, 128 | "\"Latency\": " + latency + " secs", 129 | "\"Request Duration\": " + requestDuration + " secs", 130 | "\"Serialization Duration\": " + serializationDuration + " secs", 131 | "\"Total Duration\": " + totalDuration + " secs" 132 | ] 133 | 134 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Pods/PKHUD/PKHUD/PKHUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUD.swift 3 | // PKHUD 4 | // 5 | // Created by Philip Kluz on 6/13/14. 6 | // Copyright (c) 2016 NSExceptional. All rights reserved. 7 | // Licensed under the MIT license. 8 | // 9 | 10 | import UIKit 11 | 12 | /// The PKHUD object controls showing and hiding of the HUD, as well as its contents and touch response behavior. 13 | open class PKHUD: NSObject { 14 | 15 | fileprivate struct Constants { 16 | static let sharedHUD = PKHUD() 17 | } 18 | 19 | public var viewToPresentOn: UIView? 20 | 21 | fileprivate let container = ContainerView() 22 | fileprivate var hideTimer: Timer? 23 | 24 | public typealias TimerAction = (Bool) -> Void 25 | fileprivate var timerActions = [String: TimerAction]() 26 | 27 | /// Grace period is the time (in seconds) that the invoked method may be run without 28 | /// showing the HUD. If the task finishes before the grace time runs out, the HUD will 29 | /// not be shown at all. 30 | /// This may be used to prevent HUD display for very short tasks. 31 | /// Defaults to 0 (no grace time). 32 | @available(*, deprecated, message: "Will be removed with Swift4 support, use gracePeriod instead") 33 | public var graceTime: TimeInterval { 34 | get { 35 | return gracePeriod 36 | } 37 | set(newPeriod) { 38 | gracePeriod = newPeriod 39 | } 40 | } 41 | 42 | /// Grace period is the time (in seconds) that the invoked method may be run without 43 | /// showing the HUD. If the task finishes before the grace time runs out, the HUD will 44 | /// not be shown at all. 45 | /// This may be used to prevent HUD display for very short tasks. 46 | /// Defaults to 0 (no grace time). 47 | public var gracePeriod: TimeInterval = 0 48 | fileprivate var graceTimer: Timer? 49 | 50 | // MARK: Public 51 | 52 | open class var sharedHUD: PKHUD { 53 | return Constants.sharedHUD 54 | } 55 | 56 | public override init () { 57 | super.init() 58 | NotificationCenter.default.addObserver(self, 59 | selector: #selector(PKHUD.willEnterForeground(_:)), 60 | name: NSNotification.Name.UIApplicationWillEnterForeground, 61 | object: nil) 62 | userInteractionOnUnderlyingViewsEnabled = false 63 | container.frameView.autoresizingMask = [ .flexibleLeftMargin, 64 | .flexibleRightMargin, 65 | .flexibleTopMargin, 66 | .flexibleBottomMargin ] 67 | 68 | self.container.isAccessibilityElement = true 69 | self.container.accessibilityIdentifier = "PKHUD" 70 | } 71 | 72 | public convenience init(viewToPresentOn view: UIView) { 73 | self.init() 74 | viewToPresentOn = view 75 | } 76 | 77 | deinit { 78 | NotificationCenter.default.removeObserver(self) 79 | } 80 | 81 | open var dimsBackground = true 82 | open var userInteractionOnUnderlyingViewsEnabled: Bool { 83 | get { 84 | return !container.isUserInteractionEnabled 85 | } 86 | set { 87 | container.isUserInteractionEnabled = !newValue 88 | } 89 | } 90 | 91 | open var isVisible: Bool { 92 | return !container.isHidden 93 | } 94 | 95 | open var contentView: UIView { 96 | get { 97 | return container.frameView.content 98 | } 99 | set { 100 | container.frameView.content = newValue 101 | startAnimatingContentView() 102 | } 103 | } 104 | 105 | open var effect: UIVisualEffect? { 106 | get { 107 | return container.frameView.effect 108 | } 109 | set { 110 | container.frameView.effect = newValue 111 | } 112 | } 113 | 114 | open func show(onView view: UIView? = nil) { 115 | let view: UIView = view ?? viewToPresentOn ?? UIApplication.shared.keyWindow! 116 | if !view.subviews.contains(container) { 117 | view.addSubview(container) 118 | container.frame.origin = CGPoint.zero 119 | container.frame.size = view.frame.size 120 | container.autoresizingMask = [ .flexibleHeight, .flexibleWidth ] 121 | container.isHidden = true 122 | } 123 | if dimsBackground { 124 | container.showBackground(animated: true) 125 | } 126 | 127 | // If the grace time is set, postpone the HUD display 128 | if gracePeriod > 0.0 { 129 | let timer = Timer(timeInterval: gracePeriod, target: self, selector: #selector(PKHUD.handleGraceTimer(_:)), userInfo: nil, repeats: false) 130 | RunLoop.current.add(timer, forMode: .commonModes) 131 | graceTimer = timer 132 | } else { 133 | showContent() 134 | } 135 | } 136 | 137 | func showContent() { 138 | graceTimer?.invalidate() 139 | container.showFrameView() 140 | startAnimatingContentView() 141 | } 142 | 143 | open func hide(animated anim: Bool = true, completion: TimerAction? = nil) { 144 | graceTimer?.invalidate() 145 | 146 | container.hideFrameView(animated: anim, completion: completion) 147 | stopAnimatingContentView() 148 | } 149 | 150 | open func hide(_ animated: Bool, completion: TimerAction? = nil) { 151 | hide(animated: animated, completion: completion) 152 | } 153 | 154 | open func hide(afterDelay delay: TimeInterval, completion: TimerAction? = nil) { 155 | let key = UUID().uuidString 156 | let userInfo = ["timerActionKey": key] 157 | if let completion = completion { 158 | timerActions[key] = completion 159 | } 160 | 161 | hideTimer?.invalidate() 162 | hideTimer = Timer.scheduledTimer(timeInterval: delay, 163 | target: self, 164 | selector: #selector(PKHUD.performDelayedHide(_:)), 165 | userInfo: userInfo, 166 | repeats: false) 167 | } 168 | 169 | // MARK: Internal 170 | 171 | @objc internal func willEnterForeground(_ notification: Notification?) { 172 | self.startAnimatingContentView() 173 | } 174 | 175 | internal func startAnimatingContentView() { 176 | if let animatingContentView = contentView as? PKHUDAnimating, isVisible { 177 | animatingContentView.startAnimation() 178 | } 179 | } 180 | 181 | internal func stopAnimatingContentView() { 182 | if let animatingContentView = contentView as? PKHUDAnimating { 183 | animatingContentView.stopAnimation?() 184 | } 185 | } 186 | 187 | // MARK: Timer callbacks 188 | 189 | @objc internal func performDelayedHide(_ timer: Timer? = nil) { 190 | let userInfo = timer?.userInfo as? [String:AnyObject] 191 | let key = userInfo?["timerActionKey"] as? String 192 | var completion: TimerAction? 193 | 194 | if let key = key, let action = timerActions[key] { 195 | completion = action 196 | timerActions[key] = nil 197 | } 198 | 199 | hide(animated: true, completion: completion) 200 | } 201 | 202 | @objc internal func handleGraceTimer(_ timer: Timer? = nil) { 203 | // Show the HUD only if the task is still running 204 | if (graceTimer?.isValid)! { 205 | showContent() 206 | } 207 | } 208 | } 209 | --------------------------------------------------------------------------------