diff --git a/Anyway.xcodeproj/project.pbxproj b/Anyway.xcodeproj/project.pbxproj new file mode 100644 index 0000000..88d6a1c --- /dev/null +++ b/Anyway.xcodeproj/project.pbxproj @@ -0,0 +1,663 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 30367E9B1A9D006400B16DA1 /* OCAlgorithms.m in Sources */ = {isa = PBXBuildFile; fileRef = 30367E931A9D006400B16DA1 /* OCAlgorithms.m */; }; + 30367E9C1A9D006400B16DA1 /* OCAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 30367E951A9D006400B16DA1 /* OCAnnotation.m */; }; + 30367E9D1A9D006400B16DA1 /* OCDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 30367E971A9D006400B16DA1 /* OCDistance.m */; }; + 30367E9E1A9D006400B16DA1 /* OCMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30367E9A1A9D006400B16DA1 /* OCMapView.m */; }; + 30367EA31A9D0AEB00B16DA1 /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30367EA21A9D0AEB00B16DA1 /* InsetLabel.swift */; }; + 30667BAA1A92698900853125 /* Marker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30667BA91A92698900853125 /* Marker.swift */; }; + 3097F9B41B959CDA0037FFF6 /* MarkerViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3097F9B31B959CDA0037FFF6 /* MarkerViews.swift */; }; + 309DD0461BFA464100AF80D1 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309DD0451BFA464100AF80D1 /* Utilities.swift */; }; + 30AF036D1A9BC663008B7889 /* AnnotationCoordinateUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 30AF036C1A9BC663008B7889 /* AnnotationCoordinateUtility.m */; }; + 30BB76821AEE938600CB1EBD /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BB76811AEE938600CB1EBD /* InfoViewController.swift */; }; + 30BB76841AEE9FBD00CB1EBD /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BB76831AEE9FBD00CB1EBD /* DetailViewController.swift */; }; + 30BE3E321B7918C90092CEAB /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BE3E311B7918C90092CEAB /* Constants.swift */; }; + 30BEB4621A9D1A7A005F3205 /* JGProgressHUD Resources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 30BEB44D1A9D1A7A005F3205 /* JGProgressHUD Resources.bundle */; }; + 30BEB4631A9D1A7A005F3205 /* JGProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB44F1A9D1A7A005F3205 /* JGProgressHUD.m */; }; + 30BEB4641A9D1A7A005F3205 /* JGProgressHUDAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4511A9D1A7A005F3205 /* JGProgressHUDAnimation.m */; }; + 30BEB4651A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4531A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.m */; }; + 30BEB4661A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4551A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.m */; }; + 30BEB4671A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4571A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.m */; }; + 30BEB4681A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4591A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.m */; }; + 30BEB4691A9D1A7A005F3205 /* JGProgressHUDIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB45B1A9D1A7A005F3205 /* JGProgressHUDIndicatorView.m */; }; + 30BEB46A1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB45D1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.m */; }; + 30BEB46B1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB45F1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.m */; }; + 30BEB46C1A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30BEB4611A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.m */; }; + 30CC256F1B4B11A800BE8FE9 /* AccidentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CC256E1B4B11A800BE8FE9 /* AccidentsViewController.swift */; }; + 30CCFDF31AC9E4F90000FCA2 /* FilterCellTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCFDF21AC9E4F90000FCA2 /* FilterCellTableViewCell.swift */; }; + 30CDE5111BFA525800BBC183 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30CDE5101BFA525800BBC183 /* Alamofire.framework */; }; + 30CDE5131BFA525B00BBC183 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30CDE5121BFA525B00BBC183 /* SwiftyJSON.framework */; }; + 30CDE5151BFA555500BBC183 /* Alamofire-SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CDE5141BFA555500BBC183 /* Alamofire-SwiftyJSON.swift */; }; + 30CDE5171BFA690A00BBC183 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CDE5161BFA690A00BBC183 /* Localization.swift */; }; + 30D6E5B21A92639500337FDB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D6E5B11A92639500337FDB /* AppDelegate.swift */; }; + 30D6E5B41A92639500337FDB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D6E5B31A92639500337FDB /* ViewController.swift */; }; + 30D6E5B71A92639500337FDB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 30D6E5B51A92639500337FDB /* Main.storyboard */; }; + 30D6E5B91A92639500337FDB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30D6E5B81A92639500337FDB /* Images.xcassets */; }; + 30D6E5BC1A92639500337FDB /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 30D6E5BA1A92639500337FDB /* LaunchScreen.xib */; }; + 30D6E5D21A92642000337FDB /* MapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D6E5D11A92642000337FDB /* MapProvider.swift */; }; + 30D6E5D61A92647300337FDB /* PrintlnMagic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D6E5D31A92647300337FDB /* PrintlnMagic.swift */; }; + 30DAA0981AC1F95D001F67D2 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DAA0971AC1F95D001F67D2 /* Extensions.swift */; }; + 30DAA09C1AC201D7001F67D2 /* RMDateSelectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 30DAA09B1AC201D7001F67D2 /* RMDateSelectionViewController.m */; }; + 30ECD1821ADC19D800A62052 /* SwiftyUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ECD1811ADC19D800A62052 /* SwiftyUserDefaults.swift */; }; + B6977DE4C5416BC7730037BF /* Pods_Anyway.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB6018970F06DCDD50D3498C /* Pods_Anyway.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 10910DED6E67AB372C6D5F69 /* Pods-Anyway.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Anyway.release.xcconfig"; path = "Pods/Target Support Files/Pods-Anyway/Pods-Anyway.release.xcconfig"; sourceTree = ""; }; + 143F5942D9E4E7907849857B /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 30367E911A9D006400B16DA1 /* OCAlgorithmDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCAlgorithmDelegate.h; sourceTree = ""; }; + 30367E921A9D006400B16DA1 /* OCAlgorithms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCAlgorithms.h; sourceTree = ""; }; + 30367E931A9D006400B16DA1 /* OCAlgorithms.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCAlgorithms.m; sourceTree = ""; }; + 30367E941A9D006400B16DA1 /* OCAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCAnnotation.h; sourceTree = ""; }; + 30367E951A9D006400B16DA1 /* OCAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCAnnotation.m; sourceTree = ""; }; + 30367E961A9D006400B16DA1 /* OCDistance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCDistance.h; sourceTree = ""; }; + 30367E971A9D006400B16DA1 /* OCDistance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCDistance.m; sourceTree = ""; }; + 30367E981A9D006400B16DA1 /* OCGrouping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCGrouping.h; sourceTree = ""; }; + 30367E991A9D006400B16DA1 /* OCMapView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMapView.h; sourceTree = ""; }; + 30367E9A1A9D006400B16DA1 /* OCMapView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMapView.m; sourceTree = ""; }; + 30367EA21A9D0AEB00B16DA1 /* InsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsetLabel.swift; sourceTree = ""; }; + 305E9AEF1BFA4E08003858F3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "Pods/../build/Debug-iphoneos/Alamofire.framework"; sourceTree = ""; }; + 305E9AF11BFA4E0C003858F3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = "Pods/../build/Debug-iphoneos/SwiftyJSON.framework"; sourceTree = ""; }; + 30667BA91A92698900853125 /* Marker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Marker.swift; sourceTree = ""; }; + 3097F9B31B959CDA0037FFF6 /* MarkerViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkerViews.swift; sourceTree = ""; }; + 309DD0451BFA464100AF80D1 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 30AF03671A9BC62C008B7889 /* Anyway-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Anyway-Bridging-Header.h"; sourceTree = ""; }; + 30AF036B1A9BC663008B7889 /* AnnotationCoordinateUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnnotationCoordinateUtility.h; sourceTree = ""; }; + 30AF036C1A9BC663008B7889 /* AnnotationCoordinateUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnnotationCoordinateUtility.m; sourceTree = ""; }; + 30BA64A41BFA505900700C72 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "Pods/../build/Debug-iphoneos/Alamofire.framework"; sourceTree = ""; }; + 30BA64A61BFA505C00700C72 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = "Pods/../build/Debug-iphoneos/SwiftyJSON.framework"; sourceTree = ""; }; + 30BB76811AEE938600CB1EBD /* InfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = ""; }; + 30BB76831AEE9FBD00CB1EBD /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; + 30BE3E311B7918C90092CEAB /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 30BEB44D1A9D1A7A005F3205 /* JGProgressHUD Resources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "JGProgressHUD Resources.bundle"; sourceTree = ""; }; + 30BEB44E1A9D1A7A005F3205 /* JGProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUD.h; sourceTree = ""; }; + 30BEB44F1A9D1A7A005F3205 /* JGProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUD.m; sourceTree = ""; }; + 30BEB4501A9D1A7A005F3205 /* JGProgressHUDAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDAnimation.h; sourceTree = ""; }; + 30BEB4511A9D1A7A005F3205 /* JGProgressHUDAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDAnimation.m; sourceTree = ""; }; + 30BEB4521A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDErrorIndicatorView.h; sourceTree = ""; }; + 30BEB4531A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDErrorIndicatorView.m; sourceTree = ""; }; + 30BEB4541A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDFadeAnimation.h; sourceTree = ""; }; + 30BEB4551A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDFadeAnimation.m; sourceTree = ""; }; + 30BEB4561A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDFadeZoomAnimation.h; sourceTree = ""; }; + 30BEB4571A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDFadeZoomAnimation.m; sourceTree = ""; }; + 30BEB4581A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDIndeterminateIndicatorView.h; sourceTree = ""; }; + 30BEB4591A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDIndeterminateIndicatorView.m; sourceTree = ""; }; + 30BEB45A1A9D1A7A005F3205 /* JGProgressHUDIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDIndicatorView.h; sourceTree = ""; }; + 30BEB45B1A9D1A7A005F3205 /* JGProgressHUDIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDIndicatorView.m; sourceTree = ""; }; + 30BEB45C1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDPieIndicatorView.h; sourceTree = ""; }; + 30BEB45D1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDPieIndicatorView.m; sourceTree = ""; }; + 30BEB45E1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDRingIndicatorView.h; sourceTree = ""; }; + 30BEB45F1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDRingIndicatorView.m; sourceTree = ""; }; + 30BEB4601A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JGProgressHUDSuccessIndicatorView.h; sourceTree = ""; }; + 30BEB4611A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JGProgressHUDSuccessIndicatorView.m; sourceTree = ""; }; + 30CC256E1B4B11A800BE8FE9 /* AccidentsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccidentsViewController.swift; sourceTree = ""; }; + 30CCFDF21AC9E4F90000FCA2 /* FilterCellTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterCellTableViewCell.swift; sourceTree = ""; }; + 30CDE5101BFA525800BBC183 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "Pods/../build/Debug-iphoneos/Alamofire.framework"; sourceTree = ""; }; + 30CDE5121BFA525B00BBC183 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = "Pods/../build/Debug-iphoneos/SwiftyJSON.framework"; sourceTree = ""; }; + 30CDE5141BFA555500BBC183 /* Alamofire-SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Alamofire-SwiftyJSON.swift"; sourceTree = ""; }; + 30CDE5161BFA690A00BBC183 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + 30D6E5AC1A92639500337FDB /* Anyway.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Anyway.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 30D6E5B01A92639500337FDB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 30D6E5B11A92639500337FDB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 30D6E5B31A92639500337FDB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 30D6E5B61A92639500337FDB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 30D6E5B81A92639500337FDB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 30D6E5BB1A92639500337FDB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 30D6E5C61A92639500337FDB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 30D6E5C71A92639500337FDB /* AnywayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnywayTests.swift; sourceTree = ""; }; + 30D6E5D11A92642000337FDB /* MapProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapProvider.swift; sourceTree = ""; }; + 30D6E5D31A92647300337FDB /* PrintlnMagic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrintlnMagic.swift; sourceTree = ""; }; + 30DAA0971AC1F95D001F67D2 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 30DAA09A1AC201D7001F67D2 /* RMDateSelectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMDateSelectionViewController.h; sourceTree = ""; }; + 30DAA09B1AC201D7001F67D2 /* RMDateSelectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMDateSelectionViewController.m; sourceTree = ""; }; + 30ECD1811ADC19D800A62052 /* SwiftyUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyUserDefaults.swift; sourceTree = ""; }; + 3C14099384292C4AA2F8CB67 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + CB6018970F06DCDD50D3498C /* Pods_Anyway.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Anyway.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E1EC4E31165078E14AEFDED2 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + FA18FE0DFB1949A6A2E3A1F4 /* Pods-Anyway.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Anyway.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Anyway/Pods-Anyway.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 30D6E5A91A92639500337FDB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 30CDE5131BFA525B00BBC183 /* SwiftyJSON.framework in Frameworks */, + 30CDE5111BFA525800BBC183 /* Alamofire.framework in Frameworks */, + B6977DE4C5416BC7730037BF /* Pods_Anyway.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02CAB310BEC1824B90D03798 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 30CDE5121BFA525B00BBC183 /* SwiftyJSON.framework */, + 30CDE5101BFA525800BBC183 /* Alamofire.framework */, + 30BA64A61BFA505C00700C72 /* SwiftyJSON.framework */, + 30BA64A41BFA505900700C72 /* Alamofire.framework */, + 305E9AF11BFA4E0C003858F3 /* SwiftyJSON.framework */, + 305E9AEF1BFA4E08003858F3 /* Alamofire.framework */, + 143F5942D9E4E7907849857B /* Pods.framework */, + CB6018970F06DCDD50D3498C /* Pods_Anyway.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 30367E901A9D006400B16DA1 /* OCMapView */ = { + isa = PBXGroup; + children = ( + 30367E911A9D006400B16DA1 /* OCAlgorithmDelegate.h */, + 30367E921A9D006400B16DA1 /* OCAlgorithms.h */, + 30367E931A9D006400B16DA1 /* OCAlgorithms.m */, + 30367E941A9D006400B16DA1 /* OCAnnotation.h */, + 30367E951A9D006400B16DA1 /* OCAnnotation.m */, + 30367E961A9D006400B16DA1 /* OCDistance.h */, + 30367E971A9D006400B16DA1 /* OCDistance.m */, + 30367E981A9D006400B16DA1 /* OCGrouping.h */, + 30367E991A9D006400B16DA1 /* OCMapView.h */, + 30367E9A1A9D006400B16DA1 /* OCMapView.m */, + ); + path = OCMapView; + sourceTree = ""; + }; + 30367E9F1A9D0AA300B16DA1 /* UI */ = { + isa = PBXGroup; + children = ( + 30D6E5B51A92639500337FDB /* Main.storyboard */, + 30367EA11A9D0ACC00B16DA1 /* Views */, + 30D6E5B31A92639500337FDB /* ViewController.swift */, + 30BB76811AEE938600CB1EBD /* InfoViewController.swift */, + 30BB76831AEE9FBD00CB1EBD /* DetailViewController.swift */, + 30CC256E1B4B11A800BE8FE9 /* AccidentsViewController.swift */, + ); + name = UI; + sourceTree = ""; + }; + 30367EA01A9D0AAE00B16DA1 /* Business */ = { + isa = PBXGroup; + children = ( + 30D6E5B11A92639500337FDB /* AppDelegate.swift */, + 30D6E5D11A92642000337FDB /* MapProvider.swift */, + 30667BA91A92698900853125 /* Marker.swift */, + 3097F9B31B959CDA0037FFF6 /* MarkerViews.swift */, + 30BE3E311B7918C90092CEAB /* Constants.swift */, + 30CDE5161BFA690A00BBC183 /* Localization.swift */, + 30DAA0971AC1F95D001F67D2 /* Extensions.swift */, + 309DD0451BFA464100AF80D1 /* Utilities.swift */, + ); + name = Business; + sourceTree = ""; + }; + 30367EA11A9D0ACC00B16DA1 /* Views */ = { + isa = PBXGroup; + children = ( + 30367EA21A9D0AEB00B16DA1 /* InsetLabel.swift */, + 30CCFDF21AC9E4F90000FCA2 /* FilterCellTableViewCell.swift */, + ); + name = Views; + sourceTree = ""; + }; + 30AF03661A9BC620008B7889 /* ObjC */ = { + isa = PBXGroup; + children = ( + 30DAA0991AC201D7001F67D2 /* RMDateSelectionViewController */, + 30BEB44C1A9D1A7A005F3205 /* JGProgressHUD */, + 30367E901A9D006400B16DA1 /* OCMapView */, + 30AF03671A9BC62C008B7889 /* Anyway-Bridging-Header.h */, + 30AF036B1A9BC663008B7889 /* AnnotationCoordinateUtility.h */, + 30AF036C1A9BC663008B7889 /* AnnotationCoordinateUtility.m */, + ); + name = ObjC; + sourceTree = ""; + }; + 30BEB44C1A9D1A7A005F3205 /* JGProgressHUD */ = { + isa = PBXGroup; + children = ( + 30BEB44D1A9D1A7A005F3205 /* JGProgressHUD Resources.bundle */, + 30BEB44E1A9D1A7A005F3205 /* JGProgressHUD.h */, + 30BEB44F1A9D1A7A005F3205 /* JGProgressHUD.m */, + 30BEB4501A9D1A7A005F3205 /* JGProgressHUDAnimation.h */, + 30BEB4511A9D1A7A005F3205 /* JGProgressHUDAnimation.m */, + 30BEB4521A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.h */, + 30BEB4531A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.m */, + 30BEB4541A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.h */, + 30BEB4551A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.m */, + 30BEB4561A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.h */, + 30BEB4571A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.m */, + 30BEB4581A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.h */, + 30BEB4591A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.m */, + 30BEB45A1A9D1A7A005F3205 /* JGProgressHUDIndicatorView.h */, + 30BEB45B1A9D1A7A005F3205 /* JGProgressHUDIndicatorView.m */, + 30BEB45C1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.h */, + 30BEB45D1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.m */, + 30BEB45E1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.h */, + 30BEB45F1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.m */, + 30BEB4601A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.h */, + 30BEB4611A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.m */, + ); + path = JGProgressHUD; + sourceTree = ""; + }; + 30D6E5A31A92639500337FDB = { + isa = PBXGroup; + children = ( + 30D6E5AE1A92639500337FDB /* Anyway */, + 30D6E5C41A92639500337FDB /* AnywayTests */, + 30D6E5AD1A92639500337FDB /* Products */, + DEC8F544B970873B802BCFA0 /* Pods */, + 02CAB310BEC1824B90D03798 /* Frameworks */, + ); + sourceTree = ""; + }; + 30D6E5AD1A92639500337FDB /* Products */ = { + isa = PBXGroup; + children = ( + 30D6E5AC1A92639500337FDB /* Anyway.app */, + ); + name = Products; + sourceTree = ""; + }; + 30D6E5AE1A92639500337FDB /* Anyway */ = { + isa = PBXGroup; + children = ( + 30367EA01A9D0AAE00B16DA1 /* Business */, + 30367E9F1A9D0AA300B16DA1 /* UI */, + 30D6E5AF1A92639500337FDB /* Supporting Files */, + 30AF03661A9BC620008B7889 /* ObjC */, + 30D6E5D91A92647800337FDB /* Swift Libraries */, + ); + path = Anyway; + sourceTree = ""; + }; + 30D6E5AF1A92639500337FDB /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 30D6E5BA1A92639500337FDB /* LaunchScreen.xib */, + 30D6E5B81A92639500337FDB /* Images.xcassets */, + 30D6E5B01A92639500337FDB /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 30D6E5C41A92639500337FDB /* AnywayTests */ = { + isa = PBXGroup; + children = ( + 30D6E5C71A92639500337FDB /* AnywayTests.swift */, + 30D6E5C51A92639500337FDB /* Supporting Files */, + ); + path = AnywayTests; + sourceTree = ""; + }; + 30D6E5C51A92639500337FDB /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 30D6E5C61A92639500337FDB /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 30D6E5D91A92647800337FDB /* Swift Libraries */ = { + isa = PBXGroup; + children = ( + 30CDE5141BFA555500BBC183 /* Alamofire-SwiftyJSON.swift */, + 30D6E5D31A92647300337FDB /* PrintlnMagic.swift */, + 30ECD1811ADC19D800A62052 /* SwiftyUserDefaults.swift */, + ); + name = "Swift Libraries"; + sourceTree = ""; + }; + 30DAA0991AC201D7001F67D2 /* RMDateSelectionViewController */ = { + isa = PBXGroup; + children = ( + 30DAA09A1AC201D7001F67D2 /* RMDateSelectionViewController.h */, + 30DAA09B1AC201D7001F67D2 /* RMDateSelectionViewController.m */, + ); + path = RMDateSelectionViewController; + sourceTree = ""; + }; + DEC8F544B970873B802BCFA0 /* Pods */ = { + isa = PBXGroup; + children = ( + E1EC4E31165078E14AEFDED2 /* Pods.debug.xcconfig */, + 3C14099384292C4AA2F8CB67 /* Pods.release.xcconfig */, + 10910DED6E67AB372C6D5F69 /* Pods-Anyway.release.xcconfig */, + FA18FE0DFB1949A6A2E3A1F4 /* Pods-Anyway.debug.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 30D6E5AB1A92639500337FDB /* Anyway */ = { + isa = PBXNativeTarget; + buildConfigurationList = 30D6E5CB1A92639500337FDB /* Build configuration list for PBXNativeTarget "Anyway" */; + buildPhases = ( + 06E169EFAFAFD4B2E90F5BD3 /* Check Pods Manifest.lock */, + 30D6E5A81A92639500337FDB /* Sources */, + 30D6E5A91A92639500337FDB /* Frameworks */, + 30D6E5AA1A92639500337FDB /* Resources */, + EA2D96AACACC44658F1623AE /* Embed Pods Frameworks */, + 6483DF0C888657FB42C723DE /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Anyway; + productName = Anyway; + productReference = 30D6E5AC1A92639500337FDB /* Anyway.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 30D6E5A41A92639500337FDB /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftMigration = 0710; + LastSwiftUpdateCheck = 0710; + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Hasadna; + TargetAttributes = { + 30D6E5AB1A92639500337FDB = { + CreatedOnToolsVersion = 6.1.1; + DevelopmentTeam = 85JPBD89LM; + }; + }; + }; + buildConfigurationList = 30D6E5A71A92639500337FDB /* Build configuration list for PBXProject "Anyway" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 30D6E5A31A92639500337FDB; + productRefGroup = 30D6E5AD1A92639500337FDB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 30D6E5AB1A92639500337FDB /* Anyway */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 30D6E5AA1A92639500337FDB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 30BEB4621A9D1A7A005F3205 /* JGProgressHUD Resources.bundle in Resources */, + 30D6E5B71A92639500337FDB /* Main.storyboard in Resources */, + 30D6E5BC1A92639500337FDB /* LaunchScreen.xib in Resources */, + 30D6E5B91A92639500337FDB /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06E169EFAFAFD4B2E90F5BD3 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 6483DF0C888657FB42C723DE /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + EA2D96AACACC44658F1623AE /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 30D6E5A81A92639500337FDB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 30367E9D1A9D006400B16DA1 /* OCDistance.m in Sources */, + 30D6E5D21A92642000337FDB /* MapProvider.swift in Sources */, + 30BEB4661A9D1A7A005F3205 /* JGProgressHUDFadeAnimation.m in Sources */, + 30367E9B1A9D006400B16DA1 /* OCAlgorithms.m in Sources */, + 30BEB4691A9D1A7A005F3205 /* JGProgressHUDIndicatorView.m in Sources */, + 30BEB46A1A9D1A7A005F3205 /* JGProgressHUDPieIndicatorView.m in Sources */, + 30667BAA1A92698900853125 /* Marker.swift in Sources */, + 30BE3E321B7918C90092CEAB /* Constants.swift in Sources */, + 30CCFDF31AC9E4F90000FCA2 /* FilterCellTableViewCell.swift in Sources */, + 30ECD1821ADC19D800A62052 /* SwiftyUserDefaults.swift in Sources */, + 30BB76841AEE9FBD00CB1EBD /* DetailViewController.swift in Sources */, + 30BEB4671A9D1A7A005F3205 /* JGProgressHUDFadeZoomAnimation.m in Sources */, + 30BB76821AEE938600CB1EBD /* InfoViewController.swift in Sources */, + 30DAA09C1AC201D7001F67D2 /* RMDateSelectionViewController.m in Sources */, + 30BEB4681A9D1A7A005F3205 /* JGProgressHUDIndeterminateIndicatorView.m in Sources */, + 30367EA31A9D0AEB00B16DA1 /* InsetLabel.swift in Sources */, + 30D6E5D61A92647300337FDB /* PrintlnMagic.swift in Sources */, + 30CC256F1B4B11A800BE8FE9 /* AccidentsViewController.swift in Sources */, + 30CDE5151BFA555500BBC183 /* Alamofire-SwiftyJSON.swift in Sources */, + 30367E9E1A9D006400B16DA1 /* OCMapView.m in Sources */, + 30BEB4631A9D1A7A005F3205 /* JGProgressHUD.m in Sources */, + 30BEB4641A9D1A7A005F3205 /* JGProgressHUDAnimation.m in Sources */, + 30AF036D1A9BC663008B7889 /* AnnotationCoordinateUtility.m in Sources */, + 30CDE5171BFA690A00BBC183 /* Localization.swift in Sources */, + 309DD0461BFA464100AF80D1 /* Utilities.swift in Sources */, + 30D6E5B41A92639500337FDB /* ViewController.swift in Sources */, + 30BEB4651A9D1A7A005F3205 /* JGProgressHUDErrorIndicatorView.m in Sources */, + 30367E9C1A9D006400B16DA1 /* OCAnnotation.m in Sources */, + 30DAA0981AC1F95D001F67D2 /* Extensions.swift in Sources */, + 30BEB46B1A9D1A7A005F3205 /* JGProgressHUDRingIndicatorView.m in Sources */, + 30BEB46C1A9D1A7A005F3205 /* JGProgressHUDSuccessIndicatorView.m in Sources */, + 3097F9B41B959CDA0037FFF6 /* MarkerViews.swift in Sources */, + 30D6E5B21A92639500337FDB /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 30D6E5B51A92639500337FDB /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 30D6E5B61A92639500337FDB /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 30D6E5BA1A92639500337FDB /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 30D6E5BB1A92639500337FDB /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 30A2BDAE1BFA4F4100550D88 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Debug; + }; + 30A2BDAF1BFA4F4100550D88 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FA18FE0DFB1949A6A2E3A1F4 /* Pods-Anyway.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/build/Debug-iphoneos", + ); + INFOPLIST_FILE = Anyway/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.sensiya.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Anyway/Anyway-Bridging-Header.h"; + }; + name = Debug; + }; + 30D6E5CA1A92639500337FDB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 30D6E5CD1A92639500337FDB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 10910DED6E67AB372C6D5F69 /* Pods-Anyway.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/build/Debug-iphoneos", + ); + INFOPLIST_FILE = Anyway/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.sensiya.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Anyway/Anyway-Bridging-Header.h"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 30D6E5A71A92639500337FDB /* Build configuration list for PBXProject "Anyway" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 30D6E5CA1A92639500337FDB /* Release */, + 30A2BDAE1BFA4F4100550D88 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 30D6E5CB1A92639500337FDB /* Build configuration list for PBXNativeTarget "Anyway" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 30D6E5CD1A92639500337FDB /* Release */, + 30A2BDAF1BFA4F4100550D88 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 30D6E5A41A92639500337FDB /* Project object */; +} diff --git a/Anyway.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Anyway.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..80b209c --- /dev/null +++ b/Anyway.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Anyway.xcworkspace/contents.xcworkspacedata b/Anyway.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..55a5cfd --- /dev/null +++ b/Anyway.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Anyway/AccidentsViewController.swift b/Anyway/AccidentsViewController.swift new file mode 100644 index 0000000..23eb489 --- /dev/null +++ b/Anyway/AccidentsViewController.swift @@ -0,0 +1,63 @@ +// +// AccidentsViewController.swift +// Anyway +// +// Created by Aviel Gross on 7/6/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +class AccidentsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + static let segueId = "show accidents" + + var dataSource = [Marker]() + + @IBOutlet weak var tableView: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.titleView = UIImageView(image: UIImage(named: "logo_rectangle")!) + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + tableView.deselectRowIfNeeded() + } + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + guard let + dest = segue.destinationViewController as? DetailViewController, + cell = sender as? UITableViewCell, + path = tableView.indexPathForCell(cell), + marker = dataSource.safeRetrieveElement(path.row) + else {return} + + dest.detailData = marker + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource.count + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCellWithIdentifier("detail cell", forIndexPath: indexPath) + + let data = dataSource[indexPath.row] + + if let name = data.iconName { + cell.imageView?.image = UIImage(named: name) + } + + cell.textLabel?.text = data.title ?? "" + cell.detailTextLabel?.text = "\(data.subtitle ?? "") \(data.created.shortDate)" + + + return cell + } + + @IBAction func ActionClose(sender: UIBarButtonItem) { + (self.presentingViewController ?? self.navigationController?.presentingViewController)?.dismissViewControllerAnimated(true) { } + } +} diff --git a/Anyway/Alamofire-SwiftyJSON.swift b/Anyway/Alamofire-SwiftyJSON.swift new file mode 100644 index 0000000..292dd83 --- /dev/null +++ b/Anyway/Alamofire-SwiftyJSON.swift @@ -0,0 +1,56 @@ +// +// AlamofireSwiftyJSON.swift +// AlamofireSwiftyJSON +// +// Created by Pinglin Tang on 14-9-22. +// Copyright (c) 2014 SwiftyJSON. All rights reserved. +// + +import Foundation + +import Alamofire +import SwiftyJSON + +// MARK: - Request for Swift JSON + +extension Request { + + /** + Adds a handler to be called once the request has finished. + + :param: completionHandler A closure to be executed once the request has finished. The closure takes 4 arguments: the URL request, the URL response, if one was received, the SwiftyJSON enum, if one could be created from the URL response and data, and any error produced while creating the SwiftyJSON enum. + + :returns: The request. + */ + public func responseSwiftyJSON(completionHandler: (NSURLRequest, NSHTTPURLResponse?, SwiftyJSON.JSON, NSError?) -> Void) -> Self { + return responseSwiftyJSON(nil, options:NSJSONReadingOptions.AllowFragments, completionHandler:completionHandler) + } + + /** + Adds a handler to be called once the request has finished. + + :param: queue The queue on which the completion handler is dispatched. + :param: options The JSON serialization reading options. `.AllowFragments` by default. + :param: completionHandler A closure to be executed once the request has finished. The closure takes 4 arguments: the URL request, the URL response, if one was received, the SwiftyJSON enum, if one could be created from the URL response and data, and any error produced while creating the SwiftyJSON enum. + + :returns: The request. + */ + public func responseSwiftyJSON(queue: dispatch_queue_t? = nil, options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, JSON, NSError?) -> Void) -> Self { + + return response(queue: queue, responseSerializer: Request.JSONResponseSerializer(options: options), completionHandler: { ( response ) -> Void in + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { + var responseJSON: JSON + if response.result.isFailure + { + responseJSON = JSON.null + } else { + responseJSON = SwiftyJSON.JSON(response.result.value!) + } + dispatch_async(queue ?? dispatch_get_main_queue(), { + completionHandler(self.request!, self.response, responseJSON, response.result.error as NSError?) + }) + }) + }) + } +} + diff --git a/Anyway/AnnotationCoordinateUtility.h b/Anyway/AnnotationCoordinateUtility.h new file mode 100644 index 0000000..421f827 --- /dev/null +++ b/Anyway/AnnotationCoordinateUtility.h @@ -0,0 +1,33 @@ +// +// AnnotationCoordinateUtility.h +// Anyway +// +// Created by Aviel Gross on 2/23/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +#import +@import MapKit; + +@interface AnnotationCoordinateUtility : NSObject +/* + Take [Annotation] and mutate everything that needed + */ ++ (void)mutateCoordinatesOfClashingAnnotations:(NSArray *)annotations; + + +/* + @return [NSValue:Annotation] of all points in given coordinate grouped by coordinates + */ ++ (NSDictionary *)groupAnnotationsByLocationValue:(NSArray *)annotations; + +/* + @return [Annotation] of all points in given coordinate + */ ++ (NSArray *)groupForCoodinate:(CLLocationCoordinate2D)coord fromAnnotations:(NSArray *)annotations; + +/* + @param annotations [Annotation] at the same coordinate + */ ++ (void)repositionAnnotations:(NSArray *)annotations toAvoidClashAtCoordination:(CLLocationCoordinate2D)coordinate circleDistanceDelta:(double)delta; +@end diff --git a/Anyway/AnnotationCoordinateUtility.m b/Anyway/AnnotationCoordinateUtility.m new file mode 100644 index 0000000..2f806a5 --- /dev/null +++ b/Anyway/AnnotationCoordinateUtility.m @@ -0,0 +1,95 @@ +// +// AnnotationCoordinateUtility.m +// Anyway +// +// Created by Aviel Gross on 2/23/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +#import "AnnotationCoordinateUtility.h" + +@implementation AnnotationCoordinateUtility + ++ (NSArray *)groupForCoodinate:(CLLocationCoordinate2D)coord fromAnnotations:(NSArray *)annotations { + NSDictionary *coordinateValuesToAnnotations = [self groupAnnotationsByLocationValue:annotations]; + NSValue *value = [self valueFromCoordinate:coord]; + return coordinateValuesToAnnotations[value]; +} + ++ (void)mutateCoordinatesOfClashingAnnotations:(NSArray *)annotations { + + NSDictionary *coordinateValuesToAnnotations = [self groupAnnotationsByLocationValue:annotations]; + + for (NSValue *coordinateValue in coordinateValuesToAnnotations.allKeys) { + NSMutableArray *outletsAtLocation = coordinateValuesToAnnotations[coordinateValue]; + if (outletsAtLocation.count > 1) { + CLLocationCoordinate2D coordinate; + [coordinateValue getValue:&coordinate]; + [self repositionAnnotations:outletsAtLocation toAvoidClashAtCoordination:coordinate circleDistanceDelta:13]; + } + } +} + ++ (NSValue *)valueFromCoordinate:(CLLocationCoordinate2D)coord { + return [NSValue valueWithBytes:&coord objCType:@encode(CLLocationCoordinate2D)]; +} + ++ (NSDictionary *)groupAnnotationsByLocationValue:(NSArray *)annotations { + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + for (id pin in annotations) { + + //Change coord to NSObject key + CLLocationCoordinate2D coordinate = pin.coordinate; + NSValue *coordinateValue = [self valueFromCoordinate:coordinate]; + + //Get all the values of this coord object key + NSMutableArray *annotationsAtLocation = result[coordinateValue]; + + //If none - create an array with this annotations inside + if (!annotationsAtLocation) { + annotationsAtLocation = [NSMutableArray array]; + result[coordinateValue] = annotationsAtLocation; + } + + //If any - add this annotation to the array + [annotationsAtLocation addObject:pin]; + } + return result; +} + ++ (void)repositionAnnotations:(NSArray *)annotations toAvoidClashAtCoordination:(CLLocationCoordinate2D)coordinate circleDistanceDelta:(double)delta { + + double distance = delta * annotations.count / 2.0; + double radiansBetweenAnnotations = (M_PI * 2) / annotations.count; + + for (int i = 0; i < annotations.count; i++) { + + double heading = radiansBetweenAnnotations * i; + CLLocationCoordinate2D newCoordinate = [self calculateCoordinateFrom:coordinate onBearing:heading atDistance:distance]; + + id annotation = annotations[i]; + + [annotation setCoordinate:newCoordinate]; + //annotation.coordinate = newCoordinate; + } +} + ++ (CLLocationCoordinate2D)calculateCoordinateFrom:(CLLocationCoordinate2D)coordinate onBearing:(double)bearingInRadians atDistance:(double)distanceInMetres { + + double coordinateLatitudeInRadians = coordinate.latitude * M_PI / 180; + double coordinateLongitudeInRadians = coordinate.longitude * M_PI / 180; + + double distanceComparedToEarth = distanceInMetres / 6378100; + + double resultLatitudeInRadians = asin(sin(coordinateLatitudeInRadians) * cos(distanceComparedToEarth) + cos(coordinateLatitudeInRadians) * sin(distanceComparedToEarth) * cos(bearingInRadians)); + double resultLongitudeInRadians = coordinateLongitudeInRadians + atan2(sin(bearingInRadians) * sin(distanceComparedToEarth) * cos(coordinateLatitudeInRadians), cos(distanceComparedToEarth) - sin(coordinateLatitudeInRadians) * sin(resultLatitudeInRadians)); + + CLLocationCoordinate2D result; + result.latitude = resultLatitudeInRadians * 180 / M_PI; + result.longitude = resultLongitudeInRadians * 180 / M_PI; + return result; +} + +@end diff --git a/Anyway/Anyway-Bridging-Header.h b/Anyway/Anyway-Bridging-Header.h new file mode 100644 index 0000000..06e1af7 --- /dev/null +++ b/Anyway/Anyway-Bridging-Header.h @@ -0,0 +1,10 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "AnnotationCoordinateUtility.h" +#import "OCMapView.h" +#import "JGProgressHUD.h" +#import "JGProgressHUDAnimation.h" +#import "JGProgressHUDFadeZoomAnimation.h" +#import "RMDateSelectionViewController.h" \ No newline at end of file diff --git a/Anyway/AppDelegate.swift b/Anyway/AppDelegate.swift new file mode 100644 index 0000000..6164bf7 --- /dev/null +++ b/Anyway/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// Anyway +// +// Created by Aviel Gross on 2/16/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(application: UIApplication) { + // 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. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(application: UIApplication) { + // 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. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(application: UIApplication) { + // 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. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/Anyway/Base.lproj/LaunchScreen.xib b/Anyway/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..7fb6828 --- /dev/null +++ b/Anyway/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Anyway/Base.lproj/Main.storyboard b/Anyway/Base.lproj/Main.storyboard new file mode 100644 index 0000000..ebab26d --- /dev/null +++ b/Anyway/Base.lproj/Main.storyboard @@ -0,0 +1,591 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Anyway/Constants.swift b/Anyway/Constants.swift new file mode 100644 index 0000000..506dd1a --- /dev/null +++ b/Anyway/Constants.swift @@ -0,0 +1,70 @@ +// +// Constants.swift +// Anyway +// +// Created by Aviel Gross on 8/10/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import Foundation + +let fallbackStartLocationCoordinate = CLLocationCoordinate2D(latitude: 32.158091269627874, longitude: 34.88087036877948) + +enum Severity: Int { + case Fatal = 1, Severe, Light, Various +} + +enum AccidentType: Int { + case CarToCar = -1 + case CarToObject = -2 + case CarToPedestrian = 1 +} + +var ACCIDENT_TYPE_CAR_TO_CAR = -1; // Synthetic type +var ACCIDENT_TYPE_CAR_TO_OBJECT = -2; // Synthetic type +var ACCIDENT_TYPE_CAR_TO_PEDESTRIAN = 1; +var ACCIDENT_TYPE_FRONT_TO_SIDE = 2; +var ACCIDENT_TYPE_FRONT_TO_REAR = 3; +var ACCIDENT_TYPE_SIDE_TO_SIDE = 4; +var ACCIDENT_TYPE_FRONT_TO_FRONT = 5; +var ACCIDENT_TYPE_WITH_STOPPED_CAR_NO_PARKING = 6; +var ACCIDENT_TYPE_WITH_STOPPED_CAR_PARKING = 7; +var ACCIDENT_TYPE_WITH_STILL_OBJECT = 8; +var ACCIDENT_TYPE_OFF_ROAD_OR_SIDEWALK = 9; +var ACCIDENT_TYPE_ROLLOVER = 10; +var ACCIDENT_TYPE_SKID = 11; +var ACCIDENT_TYPE_HIT_PASSSENGER_IN_CAR = 12; +var ACCIDENT_TYPE_FALLING_OFF_MOVING_VEHICLE = 13; +var ACCIDENT_TYPE_FIRE = 14; +var ACCIDENT_TYPE_OTHER = 15; +var ACCIDENT_TYPE_BACK_TO_FRONT = 17; +var ACCIDENT_TYPE_BACK_TO_SIDE = 18; +var ACCIDENT_TYPE_WITH_ANIMAL = 19; +var ACCIDENT_TYPE_WITH_VEHICLE_LOAD = 20; + + + +func accidentMinorTypeToType(type: Int) -> AccidentType? { + switch type { + case ACCIDENT_TYPE_CAR_TO_PEDESTRIAN: return .CarToPedestrian; + case ACCIDENT_TYPE_FRONT_TO_SIDE: return .CarToCar; + case ACCIDENT_TYPE_FRONT_TO_REAR: return .CarToCar; + case ACCIDENT_TYPE_SIDE_TO_SIDE: return .CarToCar; + case ACCIDENT_TYPE_FRONT_TO_FRONT: return .CarToCar; + case ACCIDENT_TYPE_WITH_STOPPED_CAR_NO_PARKING: return .CarToCar; + case ACCIDENT_TYPE_WITH_STOPPED_CAR_PARKING: return .CarToCar; + case ACCIDENT_TYPE_WITH_STILL_OBJECT: return .CarToObject; + case ACCIDENT_TYPE_OFF_ROAD_OR_SIDEWALK: return .CarToObject; + case ACCIDENT_TYPE_ROLLOVER: return .CarToObject; + case ACCIDENT_TYPE_SKID: return .CarToObject; + case ACCIDENT_TYPE_HIT_PASSSENGER_IN_CAR: return .CarToCar; + case ACCIDENT_TYPE_FALLING_OFF_MOVING_VEHICLE: return .CarToObject; + case ACCIDENT_TYPE_FIRE: return .CarToObject; + case ACCIDENT_TYPE_OTHER: return .CarToObject; + case ACCIDENT_TYPE_BACK_TO_FRONT: return .CarToCar; + case ACCIDENT_TYPE_BACK_TO_SIDE: return .CarToCar; + case ACCIDENT_TYPE_WITH_ANIMAL: return .CarToPedestrian; + case ACCIDENT_TYPE_WITH_VEHICLE_LOAD: return .CarToCar; + default: return nil + } +} \ No newline at end of file diff --git a/Anyway/DetailViewController.swift b/Anyway/DetailViewController.swift new file mode 100644 index 0000000..d943928 --- /dev/null +++ b/Anyway/DetailViewController.swift @@ -0,0 +1,99 @@ +// +// DetailViewController.swift +// Anyway +// +// Created by Aviel Gross on 4/27/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +class DetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { + + static let segueIdentifier = "show accident detail" + + var detailData: Marker? + + @IBOutlet weak var tableTopEdgeConstraint: NSLayoutConstraint! + @IBOutlet weak var btnClose: UIButton! + + @IBAction func dismissAction() { + dismissViewControllerAnimated(true) { } + } + + +// override func viewWillAppear(animated: Bool) { +// super.viewWillAppear(animated) +// +// if navigationController != nil { +// btnClose.removeFromSuperview() +// tableTopEdgeConstraint.constant = 0 +// } +// } + + override func viewDidLoad() { + super.viewDidLoad() + + if navigationController != nil { + btnClose.removeFromSuperview() + tableTopEdgeConstraint.constant = 0 + } + } + + //MARK: - Table View + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 15 + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCellWithIdentifier("detail cell", forIndexPath: indexPath) + cell.textLabel?.text = title(atIndex: indexPath.row) + cell.detailTextLabel?.text = info(atIndex: indexPath.row) + return cell + } + + func title(atIndex index: Int) -> String { + switch index { + case 0: return "כותרת" + case 1: return "מיקום" + case 2: return "כתובת" + case 3: return "תיאור" + case 4: return "כותרת תאונה" + case 5: return "תאריך" + case 6: return "עוקבים" + case 7: return "עוקב" + case 8: return "ID" + case 9: return "רמת דיוק" + case 10: return "חומרה" + case 11: return "תת סוג" + case 12: return "סוג" + case 13: return "משתמש" + default: return "" + } + } + + func info(atIndex index: Int) -> String { + if let data = detailData { + switch index { + case 0: return data.title ?? "" + case 1: return data.coordinate.humanDescription + case 2: return data.address + case 3: return data.descriptionContent + case 4: return data.titleAccident + case 5: return data.created.shortDescription + case 6: return "\(data.followers.count)" + case 7: return data.following ? "כן" : "לא" + case 8: return "\(data.id)" + case 9: return data.localizedAccuracy + case 10: return data.localizedSeverity + case 11: return data.localizedSubtype + case 12: return "\(data.type)" + case 13: return data.user + default: return "" + } + } + return "" + } + +} diff --git a/Anyway/Extensions.swift b/Anyway/Extensions.swift new file mode 100644 index 0000000..9e5cbf4 --- /dev/null +++ b/Anyway/Extensions.swift @@ -0,0 +1,41 @@ +// +// Extensions.swift +// Anyway +// +// Created by Aviel Gross on 3/24/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import Foundation + + +extension UIButton { + @IBInspectable var borderWidth: CGFloat { + get { return layer.borderWidth } + set { layer.borderWidth = newValue + layer.borderColor = titleLabel?.textColor.CGColor ?? layer.borderColor + } + } +} + +extension CLLocationCoordinate2D { + var humanDescription: String { + return "\(latitude),\(longitude)" + } +} + +extension MKMapView { + func visibleAnnotations() -> [MKAnnotation] { + + var visibleAnots = [MKAnnotation]() + let selfRegion = self.region + + for anot in self.annotations { + if MKCoordinateRegionContainsPoint(selfRegion, anot.coordinate) { + visibleAnots.append(anot) + } + } + + return visibleAnots + } +} \ No newline at end of file diff --git a/Anyway/FilterCellTableViewCell.swift b/Anyway/FilterCellTableViewCell.swift new file mode 100644 index 0000000..dca378c --- /dev/null +++ b/Anyway/FilterCellTableViewCell.swift @@ -0,0 +1,84 @@ +// +// FilterCellTableViewCell.swift +// Anyway +// +// Created by Aviel Gross on 3/30/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +protocol FilterCellDelegate { + func filterSwitchChanged(to: Bool, filterType: FilterCellTableViewCell.FilterType) +} + +class FilterCellTableViewCell: UITableViewCell { + + enum FilterType: Int { + case StartDate = 0, EndDate // Date pickers + case ShowFatal, ShowSevere, ShowLight, ShowInaccurate // Switches + } + + var filterType: FilterType? + weak var filter: Filter? { didSet{ updateCellUI() } } + + @IBOutlet weak var btnSwitch: UISwitch! { didSet{ updateCellUI() } } + @IBOutlet weak var titleLabel: UILabel! { didSet{ updateCellUI() } } + @IBOutlet weak var detailLabel: UILabel! { didSet{ updateCellUI() } } + + var filterCellLabel: String? { + if let type = filterType { + switch type { + case .StartDate: return "תאריך התחלה" + case .EndDate: return "תאריך סיום" + case .ShowFatal: return "הצג תאונות קטלניות" + case .ShowSevere: return "הצג פגיעות בינוניות" + case .ShowLight: return "הצג פגיעות קלות" + case .ShowInaccurate: return "הצג תאונות לא מדויקות" + } + } + return nil + } + + @IBAction func switchValueChanged(sender: UISwitch) { + if let type = filterType { + switch type { + case .ShowFatal: filter?.showFatal = btnSwitch.on + case .ShowSevere: filter?.showSevere = btnSwitch.on + case .ShowLight: filter?.showLight = btnSwitch.on + case .ShowInaccurate: filter?.showInaccurate = btnSwitch.on + default: break + } + } + } + + + func updateCellUI() { + //Labels + titleLabel?.text = filterCellLabel + + //Filter + if let type = filterType, let fil = filter { + switch type { + case .StartDate: detailLabel?.text = dateLabel(fil.startDate) + case .EndDate: detailLabel?.text = dateLabel(fil.endDate) + case .ShowFatal: btnSwitch?.on = fil.showFatal + case .ShowSevere: btnSwitch?.on = fil.showSevere + case .ShowLight: btnSwitch?.on = fil.showLight + case .ShowInaccurate: btnSwitch?.on = fil.showInaccurate + } + } + } + + func dateLabel(fromDate: NSDate?) -> String { + if let date = fromDate { + let formatter = NSDateFormatter() + formatter.locale = NSLocale.currentLocale() + formatter.timeStyle = NSDateFormatterStyle.NoStyle + formatter.dateStyle = NSDateFormatterStyle.MediumStyle + return formatter.stringFromDate(date) + } + return "בחר תאריך" + } + +} diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/Contents.json b/Anyway/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..47fd72b --- /dev/null +++ b/Anyway/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,74 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon180-3.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon180-4.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon180.png", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon180-2.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon180-1.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon120.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon120.png new file mode 100644 index 0000000..1d47d74 Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon120.png differ diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon180-1.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-1.png new file mode 100644 index 0000000..b0c17aa Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-1.png differ diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon180-2.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-2.png new file mode 100644 index 0000000..a2c9042 Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-2.png differ diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon180-3.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-3.png new file mode 100644 index 0000000..3b39b47 Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-3.png differ diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon180-4.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-4.png new file mode 100644 index 0000000..f5a13ce Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon180-4.png differ diff --git a/Anyway/Images.xcassets/AppIcon.appiconset/icon180.png b/Anyway/Images.xcassets/AppIcon.appiconset/icon180.png new file mode 100644 index 0000000..21ef8a6 Binary files /dev/null and b/Anyway/Images.xcassets/AppIcon.appiconset/icon180.png differ diff --git a/Anyway/Images.xcassets/icons/cluster_1.imageset/Contents.json b/Anyway/Images.xcassets/icons/cluster_1.imageset/Contents.json new file mode 100644 index 0000000..679104f --- /dev/null +++ b/Anyway/Images.xcassets/icons/cluster_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "cluster_1.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/cluster_1.imageset/cluster_1.png b/Anyway/Images.xcassets/icons/cluster_1.imageset/cluster_1.png new file mode 100644 index 0000000..3a4d38e Binary files /dev/null and b/Anyway/Images.xcassets/icons/cluster_1.imageset/cluster_1.png differ diff --git a/Anyway/Images.xcassets/icons/cluster_2.imageset/Contents.json b/Anyway/Images.xcassets/icons/cluster_2.imageset/Contents.json new file mode 100644 index 0000000..d71aa5c --- /dev/null +++ b/Anyway/Images.xcassets/icons/cluster_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "cluster_2.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/cluster_2.imageset/cluster_2.png b/Anyway/Images.xcassets/icons/cluster_2.imageset/cluster_2.png new file mode 100644 index 0000000..61891f3 Binary files /dev/null and b/Anyway/Images.xcassets/icons/cluster_2.imageset/cluster_2.png differ diff --git a/Anyway/Images.xcassets/icons/cluster_3.imageset/Contents.json b/Anyway/Images.xcassets/icons/cluster_3.imageset/Contents.json new file mode 100644 index 0000000..81930dd --- /dev/null +++ b/Anyway/Images.xcassets/icons/cluster_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "cluster_3.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/cluster_3.imageset/cluster_3.png b/Anyway/Images.xcassets/icons/cluster_3.imageset/cluster_3.png new file mode 100644 index 0000000..5c506a6 Binary files /dev/null and b/Anyway/Images.xcassets/icons/cluster_3.imageset/cluster_3.png differ diff --git a/Anyway/Images.xcassets/icons/cluster_4.imageset/Contents.json b/Anyway/Images.xcassets/icons/cluster_4.imageset/Contents.json new file mode 100644 index 0000000..280fb29 --- /dev/null +++ b/Anyway/Images.xcassets/icons/cluster_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "cluster_4.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/cluster_4.imageset/cluster_4.png b/Anyway/Images.xcassets/icons/cluster_4.imageset/cluster_4.png new file mode 100644 index 0000000..f8f269e Binary files /dev/null and b/Anyway/Images.xcassets/icons/cluster_4.imageset/cluster_4.png differ diff --git a/Anyway/Images.xcassets/icons/discussion.imageset/Contents.json b/Anyway/Images.xcassets/icons/discussion.imageset/Contents.json new file mode 100644 index 0000000..07f73f1 --- /dev/null +++ b/Anyway/Images.xcassets/icons/discussion.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "discussion.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "discussion@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/discussion.imageset/discussion.png b/Anyway/Images.xcassets/icons/discussion.imageset/discussion.png new file mode 100644 index 0000000..94dbe3f Binary files /dev/null and b/Anyway/Images.xcassets/icons/discussion.imageset/discussion.png differ diff --git a/Anyway/Images.xcassets/icons/discussion.imageset/discussion@2x.png b/Anyway/Images.xcassets/icons/discussion.imageset/discussion@2x.png new file mode 100644 index 0000000..eb806c1 Binary files /dev/null and b/Anyway/Images.xcassets/icons/discussion.imageset/discussion@2x.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_lethal.imageset/Contents.json b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/Contents.json new file mode 100644 index 0000000..b85a28a --- /dev/null +++ b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "multiple_lethal.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "multiple_lethal@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal.png b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal.png new file mode 100644 index 0000000..ce3125f Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal@2x.png b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal@2x.png new file mode 100644 index 0000000..300e8e9 Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_lethal.imageset/multiple_lethal@2x.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_medium.imageset/Contents.json b/Anyway/Images.xcassets/icons/multiple_medium.imageset/Contents.json new file mode 100644 index 0000000..36e7486 --- /dev/null +++ b/Anyway/Images.xcassets/icons/multiple_medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "multiple_medium.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "multiple_medium@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium.png b/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium.png new file mode 100644 index 0000000..014f643 Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium@2x.png b/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium@2x.png new file mode 100644 index 0000000..f9dc239 Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_medium.imageset/multiple_medium@2x.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_severe.imageset/Contents.json b/Anyway/Images.xcassets/icons/multiple_severe.imageset/Contents.json new file mode 100644 index 0000000..cebe717 --- /dev/null +++ b/Anyway/Images.xcassets/icons/multiple_severe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "multiple_severe.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "multiple_severe@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe.png b/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe.png new file mode 100644 index 0000000..9d13b3b Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe@2x.png b/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe@2x.png new file mode 100644 index 0000000..155bead Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_severe.imageset/multiple_severe@2x.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_various.imageset/Contents.json b/Anyway/Images.xcassets/icons/multiple_various.imageset/Contents.json new file mode 100644 index 0000000..3a6fb59 --- /dev/null +++ b/Anyway/Images.xcassets/icons/multiple_various.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "multiple_various.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "multiple_various@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various.png b/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various.png new file mode 100644 index 0000000..8e4d9e7 Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various.png differ diff --git a/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various@2x.png b/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various@2x.png new file mode 100644 index 0000000..17d96a9 Binary files /dev/null and b/Anyway/Images.xcassets/icons/multiple_various.imageset/multiple_various@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/Contents.json new file mode 100644 index 0000000..f7b1f49 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_object_lethal.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_object_lethal@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal.png b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal.png new file mode 100644 index 0000000..8dfffc9 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal@2x.png b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal@2x.png new file mode 100644 index 0000000..31c18b0 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_lethal.imageset/vehicle_object_lethal@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/Contents.json new file mode 100644 index 0000000..8d45b59 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_object_medium.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_object_medium@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium.png b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium.png new file mode 100644 index 0000000..5fb31d0 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium@2x.png b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium@2x.png new file mode 100644 index 0000000..ee4407d Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_medium.imageset/vehicle_object_medium@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/Contents.json new file mode 100644 index 0000000..8cfd72c --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_object_severe.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_object_severe@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe.png b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe.png new file mode 100644 index 0000000..212e077 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe@2x.png b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe@2x.png new file mode 100644 index 0000000..1434297 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_object_severe.imageset/vehicle_object_severe@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/Contents.json new file mode 100644 index 0000000..3e6a2e4 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_person_lethal.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_person_lethal@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal.png b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal.png new file mode 100644 index 0000000..d9430da Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal@2x.png b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal@2x.png new file mode 100644 index 0000000..431e450 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_lethal.imageset/vehicle_person_lethal@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/Contents.json new file mode 100644 index 0000000..6bdd39b --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_person_medium.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_person_medium@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium.png b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium.png new file mode 100644 index 0000000..890a09e Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium@2x.png b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium@2x.png new file mode 100644 index 0000000..c98a1f0 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_medium.imageset/vehicle_person_medium@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/Contents.json new file mode 100644 index 0000000..c599196 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_person_severe.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_person_severe@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe.png b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe.png new file mode 100644 index 0000000..b481c25 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe@2x.png b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe@2x.png new file mode 100644 index 0000000..8ab4f44 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_person_severe.imageset/vehicle_person_severe@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/Contents.json new file mode 100644 index 0000000..05a6419 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_unknown_lethal.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_unknown_lethal@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal.png b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal.png new file mode 100644 index 0000000..0504bd9 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal@2x.png b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal@2x.png new file mode 100644 index 0000000..017bb68 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_lethal.imageset/vehicle_unknown_lethal@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/Contents.json new file mode 100644 index 0000000..eda1a4f --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_unknown_medium.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_unknown_medium@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium.png b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium.png new file mode 100644 index 0000000..37b2121 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium@2x.png b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium@2x.png new file mode 100644 index 0000000..9d6da96 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_medium.imageset/vehicle_unknown_medium@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/Contents.json new file mode 100644 index 0000000..f03fed8 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_unknown_severe.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_unknown_severe@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe.png b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe.png new file mode 100644 index 0000000..45305f7 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe@2x.png b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe@2x.png new file mode 100644 index 0000000..ed6b300 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_unknown_severe.imageset/vehicle_unknown_severe@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/Contents.json new file mode 100644 index 0000000..e83f24a --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_vehicle_lethal.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_vehicle_lethal@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal.png new file mode 100644 index 0000000..4a4afae Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal@2x.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal@2x.png new file mode 100644 index 0000000..e36b0dd Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_lethal.imageset/vehicle_vehicle_lethal@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/Contents.json new file mode 100644 index 0000000..88a2978 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_vehicle_medium.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_vehicle_medium@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium.png new file mode 100644 index 0000000..6d75e37 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium@2x.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium@2x.png new file mode 100644 index 0000000..558f675 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_medium.imageset/vehicle_vehicle_medium@2x.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/Contents.json b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/Contents.json new file mode 100644 index 0000000..b849e74 --- /dev/null +++ b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "vehicle_vehicle_severe.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "vehicle_vehicle_severe@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe.png new file mode 100644 index 0000000..447f9a3 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe.png differ diff --git a/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe@2x.png b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe@2x.png new file mode 100644 index 0000000..876e0b4 Binary files /dev/null and b/Anyway/Images.xcassets/icons/vehicle_vehicle_severe.imageset/vehicle_vehicle_severe@2x.png differ diff --git a/Anyway/Images.xcassets/icons/you_are_Here.imageset/Contents.json b/Anyway/Images.xcassets/icons/you_are_Here.imageset/Contents.json new file mode 100644 index 0000000..d4c15ff --- /dev/null +++ b/Anyway/Images.xcassets/icons/you_are_Here.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "you_are_Here.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "you_are_Here@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here.png b/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here.png new file mode 100644 index 0000000..4c0ffc5 Binary files /dev/null and b/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here.png differ diff --git a/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here@2x.png b/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here@2x.png new file mode 100644 index 0000000..068c0fd Binary files /dev/null and b/Anyway/Images.xcassets/icons/you_are_Here.imageset/you_are_Here@2x.png differ diff --git a/Anyway/Images.xcassets/logo_rectangle.imageset/Contents.json b/Anyway/Images.xcassets/logo_rectangle.imageset/Contents.json new file mode 100644 index 0000000..c969319 --- /dev/null +++ b/Anyway/Images.xcassets/logo_rectangle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "anyway.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/logo_rectangle.imageset/anyway.png b/Anyway/Images.xcassets/logo_rectangle.imageset/anyway.png new file mode 100644 index 0000000..0d423b2 Binary files /dev/null and b/Anyway/Images.xcassets/logo_rectangle.imageset/anyway.png differ diff --git a/Anyway/Images.xcassets/logo_square.imageset/Contents.json b/Anyway/Images.xcassets/logo_square.imageset/Contents.json new file mode 100644 index 0000000..aa0ddf2 --- /dev/null +++ b/Anyway/Images.xcassets/logo_square.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "logo.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/logo_square.imageset/logo.png b/Anyway/Images.xcassets/logo_square.imageset/logo.png new file mode 100644 index 0000000..18e904d Binary files /dev/null and b/Anyway/Images.xcassets/logo_square.imageset/logo.png differ diff --git a/Anyway/Images.xcassets/sadna_logo.imageset/Contents.json b/Anyway/Images.xcassets/sadna_logo.imageset/Contents.json new file mode 100644 index 0000000..a742fe5 --- /dev/null +++ b/Anyway/Images.xcassets/sadna_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "sadna200x200 copy.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "sadna200x200.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200 copy.png b/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200 copy.png new file mode 100644 index 0000000..32f9080 Binary files /dev/null and b/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200 copy.png differ diff --git a/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200.png b/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200.png new file mode 100644 index 0000000..92e1bd7 Binary files /dev/null and b/Anyway/Images.xcassets/sadna_logo.imageset/sadna200x200.png differ diff --git a/Anyway/Info.plist b/Anyway/Info.plist new file mode 100644 index 0000000..5d25429 --- /dev/null +++ b/Anyway/Info.plist @@ -0,0 +1,68 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + נשתמש במיקום שלך כדי להציג את מצב הכבישים מסביבך + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + NSAppTransportSecurity + + NSExceptionDomains + + anyway.co.il + + + NSIncludesSubdomains + + + NSTemporaryExceptionAllowsInsecureHTTPLoads + + + NSTemporaryExceptionMinimumTLSVersion + TLSv1.1 + + + + + + + diff --git a/Anyway/InfoViewController.swift b/Anyway/InfoViewController.swift new file mode 100644 index 0000000..561c996 --- /dev/null +++ b/Anyway/InfoViewController.swift @@ -0,0 +1,27 @@ +// +// InfoViewController.swift +// Anyway +// +// Created by Aviel Gross on 4/27/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +class InfoViewController: UIViewController { + + @IBOutlet weak var infoLabelText: UILabel! { + didSet{ + infoLabelText.text = infoLabelText.text?.stringByForcingWritingDirectionRTL() + } + } + + @IBAction func dismissAction() { + dismissViewControllerAnimated(true) { } + } + + @IBAction func moroInfoLinkAction() { + + } + +} diff --git a/Anyway/InsetLabel.swift b/Anyway/InsetLabel.swift new file mode 100644 index 0000000..d419f41 --- /dev/null +++ b/Anyway/InsetLabel.swift @@ -0,0 +1,44 @@ +// +// InsetLabel.swift +// Anyway +// +// Created by Aviel Gross on 2/24/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit + +class InsetLabel: UILabel { + + @IBInspectable var insetVertical: CGFloat = 8 { + didSet{ + insets.bottom = insetVertical + insets.top = insetVertical + } + } + + @IBInspectable var insetHorizontal: CGFloat = 8 { + didSet{ + insets.left = insetHorizontal + insets.right = insetHorizontal + } + } + + var insets: UIEdgeInsets = UIEdgeInsetsMake(8, 8, 8, 8) + + + override func drawTextInRect(rect: CGRect) { + return super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) + } + + override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { + var rect = super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, insets), limitedToNumberOfLines: numberOfLines) + + rect.origin.x -= insets.left + rect.origin.y -= insets.top + rect.size.width += (insets.left + insets.right); + rect.size.height += (insets.top + insets.bottom); + + return rect + } +} diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error.png new file mode 100755 index 0000000..cb93516 Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@2x.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@2x.png new file mode 100755 index 0000000..8f61abd Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@2x.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@3x.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@3x.png new file mode 100755 index 0000000..40a609a Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_error@3x.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success.png new file mode 100755 index 0000000..81ebe7f Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@2x.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@2x.png new file mode 100755 index 0000000..76c0a85 Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@2x.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@3x.png b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@3x.png new file mode 100755 index 0000000..e6a9dfb Binary files /dev/null and b/Anyway/JGProgressHUD/JGProgressHUD Resources.bundle/jg_hud_success@3x.png differ diff --git a/Anyway/JGProgressHUD/JGProgressHUD.h b/Anyway/JGProgressHUD/JGProgressHUD.h new file mode 100755 index 0000000..2338c0a --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUD.h @@ -0,0 +1,364 @@ +// +// JGProgressHUD.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import +#import + +#import "JGProgressHUDIndicatorView.h" +#import "JGProgressHUDAnimation.h" + +/** + Positions of the HUD. + */ +typedef NS_ENUM(NSUInteger, JGProgressHUDPosition) { + /** Center position. */ + JGProgressHUDPositionCenter = 0, + /** Top left position. */ + JGProgressHUDPositionTopLeft, + /** Top center position. */ + JGProgressHUDPositionTopCenter, + /** Top right position. */ + JGProgressHUDPositionTopRight, + /** Center left position. */ + JGProgressHUDPositionCenterLeft, + /** Center right position. */ + JGProgressHUDPositionCenterRight, + /** Bottom left position. */ + JGProgressHUDPositionBottomLeft, + /** Bottom center position. */ + JGProgressHUDPositionBottomCenter, + /** Bottom right position. */ + JGProgressHUDPositionBottomRight +}; + +/** + Appearance styles of the HUD. + */ +typedef NS_ENUM(NSUInteger, JGProgressHUDStyle) { + /** Extra light HUD with dark elements. */ + JGProgressHUDStyleExtraLight = 0, + /** Light HUD with dark elemets. */ + JGProgressHUDStyleLight, + /** Dark HUD with light elements. */ + JGProgressHUDStyleDark +}; + +/** + Interaction types. + */ +typedef NS_ENUM(NSUInteger, JGProgressHUDInteractionType) { + /** Block all touches. No interaction behin the HUD is possible. */ + JGProgressHUDInteractionTypeBlockAllTouches = 0, + /** Block touches on the HUD view. */ + JGProgressHUDInteractionTypeBlockTouchesOnHUDView, + /** Block no touches. */ + JGProgressHUDInteractionTypeBlockNoTouches +}; + +@class JGProgressHUD; + +@protocol JGProgressHUDDelegate + +@optional + +/** + Called before the HUD will appear. + @param view The view in which the HUD is presented. + */ +- (void)progressHUD:(JGProgressHUD *)progressHUD willPresentInView:(UIView *)view; + +/** + Called after the HUD appeared. + @param view The view in which the HUD is presented. + */ +- (void)progressHUD:(JGProgressHUD *)progressHUD didPresentInView:(UIView *)view; + +/** + Called before the HUD will disappear. + @param view The view in which the HUD is presented and will be dismissed from. + */ +- (void)progressHUD:(JGProgressHUD *)progressHUD willDismissFromView:(UIView *)view; + +/** + Called after the HUD has disappeared. + @param view The view in which the HUD was presented and was be dismissed from. + */ +- (void)progressHUD:(JGProgressHUD *)progressHUD didDismissFromView:(UIView *)view; + +@end + +/** + A HUD view to indicate progress, success, error, warnings or other notifications to the user. + @note Remember to call every method from the main thread! UIKit = always main thread! + @attention This applies only to iOS 8 and higher: You may not add JGProgressHUD to a view which has an alpha value < 1.0 or to a view which is a subview of a view with an alpha value < 1.0. + */ +@interface JGProgressHUD : UIView + +/** + Always initialize JGProgressHUD using this method or it's convenience method @c progressHUDWithStyle:. + @param style The appearance style of the HUD. + */ +- (instancetype)initWithStyle:(JGProgressHUDStyle)style; + +/** + Convenience method to initialize a new HUD. + @param style The appearance style of the HUD. + */ ++ (instancetype)progressHUDWithStyle:(JGProgressHUDStyle)style; + +/** + The view in which the HUD is presented. + */ +@property (nonatomic, weak, readonly) UIView *targetView; + +/** + The delegate of the HUD. + @sa JGProgressHUDDelegate. + */ +@property (nonatomic, weak) id delegate; + +/** + A block to be invoked when the HUD view is tapped. + + @note The interaction type of the HUD must be JGProgressHUDInteractionTypeBlockTouchesOnHUDView or JGProgressHUDInteractionTypeBlockNoTouches, if not this block won't be fired. + */ +@property (nonatomic, copy) void (^tapOnHUDViewBlock)(JGProgressHUD *HUD); + +/** + A block to be invoked when the area outside of the HUD view is tapped. + + @note The interaction type of the HUD must be JGProgressHUDInteractionTypeBlockNoTouches, if not this block won't be fired. + */ +@property (nonatomic, copy) void (^tapOutsideBlock)(JGProgressHUD *HUD); + +/** + The actual HUD view visible on screen. You may add animations or shadows to this view. + */ +@property (nonatomic, strong, readonly) UIView *HUDView; + +/** + The content view inside the @c HUDView. If you want to add additional views to the HUD you should add them as subview to the @c contentView. + */ +@property (nonatomic, strong, readonly) UIView *contentView; + +/** + The label used to present text on the HUD. set the @c text property of this label to change the displayed text. You may not change the label's @c frame or @c bounds. + */ +@property (nonatomic, strong, readonly) UILabel *textLabel; + +/** + The label used to present detail text on the HUD. set the @c text property of this label to change the displayed text. You may not change the label's @c frame or @c bounds. + */ +@property (nonatomic, strong, readonly) UILabel *detailTextLabel; + +/** + The indicator view. You can assign a custom subclass of JGProgressHUDIndicatorView to this property or one of the default indicator views (if you do so, you should assign it before showing the HUD). + + @b Default: JGProgressHUDIndeterminateIndicatorView. + */ +@property (nonatomic, strong) JGProgressHUDIndicatorView *indicatorView; + +/** + Interaction type of the HUD. + + @sa JGProgressHUDInteractionType. + + @b Default: JGProgressHUDInteractionTypeBlockAllTouches. + */ +@property (nonatomic, assign) JGProgressHUDInteractionType interactionType; + +/** + The appearance style of the HUD. + + @b Default: JGProgressHUDStyleExtraLight. + */ +@property (nonatomic, assign, readonly) JGProgressHUDStyle style; + +/** + If the HUD should always have the same width and height. + + @b Default: NO. + */ +@property (nonatomic, assign) BOOL square; + +/** + The radius used for rounding the four corners of the HUD view. + + @b Default: 10.0. + */ +@property (nonatomic, assign) CGFloat cornerRadius; + +/** + Insets the contents of the HUD. + + @b Default: (20, 20, 20, 20). + */ +@property (nonatomic, assign) UIEdgeInsets contentInsets; + +/** + Insets the HUD from the frame of the hosting view or from the specified frame to present the HUD from. + + @b Default: (20, 20, 20, 20). + */ +@property (nonatomic, assign) UIEdgeInsets marginInsets; + +/** + The position of the HUD inside the hosting view's frame, or inside the specified frame. + + @b Default: JGProgressHUDPositionCenter + */ +@property (nonatomic, assign) JGProgressHUDPosition position; + +/** + The animation used for showing and dismissing the HUD. + + @b Default: JGProgressHUDFadeAnimation. + */ +@property (nonatomic, strong) JGProgressHUDAnimation *animation; + +/** + The animation duration for a layout change (ex. Changing the @c text, the @c position, the @c progressIndicatorView or the @c useProgressIndicatorView property). + + @b Default: 0.3. + */ +@property (nonatomic, assign) NSTimeInterval layoutChangeAnimationDuration; + +/** + If the HUD is visible on screen. + */ +@property (nonatomic, assign, readonly, getter = isVisible) BOOL visible; + +/** + The progress to display using the @c progressIndicatorView. A change of this property is not animated. Use the @c setProgress:animated: method for an animated progress change. + + @b Default: 0.0. + */ +@property (nonatomic, assign) float progress; + +/** + Adjusts the current progress shown by the receiver, optionally animating the change. + + The current progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. Values less than 0.0 and greater than 1.0 are pinned to those limits. + + @param progress The new progress value. + @param animated YES if the change should be animated, NO if the change should happen immediately. + */ +- (void)setProgress:(float)progress animated:(BOOL)animated; + + +///////////// +// Showing // +///////////// + + +/** + Shows the HUD animated. You should preferably show the HUD in a UIViewController's view. + @param view The view to show the HUD in. The frame of the @c view will be used to calculate the position of the HUD. + */ +- (void)showInView:(UIView *)view; + +/** + Shows the HUD. You should preferably show the HUD in a UIViewController's view. + @param view The view to show the HUD in. The frame of the @c view will be used to calculate the position of the HUD. + @param animated If th HUD should show with an animation. + */ +- (void)showInView:(UIView *)view animated:(BOOL)animated; + +/** + Shows the HUD animated. You should preferably show the HUD in a UIViewController's view. + @param view The view to show the HUD in. + @param rect The rect allocated in @c view for displaying the HUD. + */ +- (void)showInRect:(CGRect)rect inView:(UIView *)view; + +/** + Shows the HUD animated. You should preferably show the HUD in a UIViewController's view. + @param view The view to show the HUD in. + @param rect The rect allocated in @c view for displaying the HUD. + @param animated If th HUD should show with an animation. + */ +- (void)showInRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated; + + + + +//////////////// +// Dismissing // +//////////////// + + + +/** + Dismisses the HUD animated. + */ +- (void)dismiss; + +/** + Dismisses the HUD. + @param animated If the HUD should dismiss with an animation. + */ +- (void)dismissAnimated:(BOOL)animated; + +/** + Dismisses the HUD animated after a delay. + @param delay The delay until the HUD will be dismissed. + */ +- (void)dismissAfterDelay:(NSTimeInterval)delay; + +/** + Dismisses the HUD after a delay. + @param delay The delay until the HUD will be dismissed. + @param animated If the HUD should dismiss with an animation. + */ +- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated; + + +@end + + + +@interface JGProgressHUD (HUDManagement) + +/** + @param view The view to return all visible progress HUDs for. + @return All visible progress HUDs in the view. + */ ++ (NSArray *)allProgressHUDsInView:(UIView *)view; + + +/** + @param view The view to return all visible progress HUDs for. + @return All visible progress HUDs in the view and its subviews. + */ ++ (NSArray *)allProgressHUDsInViewHierarchy:(UIView *)view; + +@end + + + +@interface JGProgressHUD (Deprecated) + +/** + @warning Deprecated. Use @c indicatorView. + */ +@property (nonatomic, strong) JGProgressHUDIndicatorView *progressIndicatorView DEPRECATED_ATTRIBUTE; +/** + @warning Deprecated this no longer has any effect. To show no indicator view set @c indicatorView to @c nil, otherwise assign an indicator view to @c indicatorView (By default @c indicatorView is @c JGProgressHUDIndeterminateIndicatorView). + @sa indicatorView. + */ +@property (nonatomic, assign) BOOL useProgressIndicatorView DEPRECATED_ATTRIBUTE; + +@end + + +/** + Macro for safe floating point comparison (for internal use in JGProgressHUD). + */ +#ifndef fequal +#define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON) +#endif diff --git a/Anyway/JGProgressHUD/JGProgressHUD.m b/Anyway/JGProgressHUD/JGProgressHUD.m new file mode 100755 index 0000000..1dee300 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUD.m @@ -0,0 +1,838 @@ +// +// JGProgressHUD.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUD.h" +#import +#import "JGProgressHUDFadeAnimation.h" +#import "JGProgressHUDIndeterminateIndicatorView.h" + +#if !__has_feature(objc_arc) +#error "JGProgressHUD requires ARC!" +#endif + +#ifndef iPad +#define iPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) +#endif + +#ifndef NSFoundationVersionNumber_iOS_7_0 +#define NSFoundationVersionNumber_iOS_7_0 1047.20 +#endif + +#ifndef NSFoundationVersionNumber_iOS_8_0 +#define NSFoundationVersionNumber_iOS_8_0 1134.10 +#endif + +#ifndef iOS7 +#define iOS7 (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_7_0) +#endif + +#ifndef iOS8 +#define iOS8 (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_8_0) +#endif + +@interface JGProgressHUD () { + BOOL _transitioning; + BOOL _updateAfterAppear; + + BOOL _dismissAfterTransitionFinished; + BOOL _dismissAfterTransitionFinishedWithAnimation; + + JGProgressHUDIndicatorView *_indicatorViewAfterTransitioning; +} + +@end + +@interface JGProgressHUDAnimation (Private) + +@property (nonatomic, weak) JGProgressHUD *progressHUD; + +@end + +@implementation JGProgressHUD + +@synthesize HUDView = _HUDView; +@synthesize textLabel = _textLabel; +@synthesize detailTextLabel = _detailTextLabel; +@synthesize indicatorView = _indicatorView; +@synthesize animation = _animation; + +@dynamic visible, contentView; + +#pragma mark - Keyboard + +static CGRect keyboardFrame = (CGRect){{0.0f, 0.0f}, {0.0f, 0.0f}}; + ++ (void)keyboardFrameWillChange:(NSNotification *)notification { + keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + if (CGRectIsEmpty(keyboardFrame)) { + keyboardFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; + } +} + ++ (void)keyboardFrameDidChange:(NSNotification *)notification { + keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; +} + ++ (CGRect)currentKeyboardFrame { + return keyboardFrame; +} + ++ (void)load { + [super load]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameDidChange:) name:UIKeyboardDidChangeFrameNotification object:nil]; +} + +#pragma mark - Initializers + +- (instancetype)init { + return [self initWithStyle:JGProgressHUDStyleExtraLight]; +} + +- (instancetype)initWithFrame:(CGRect __unused)frame { + return [self initWithStyle:JGProgressHUDStyleExtraLight]; +} + +- (instancetype)initWithStyle:(JGProgressHUDStyle)style { + self = [super initWithFrame:CGRectZero]; + + if (self) { + _style = style; + + self.hidden = YES; + self.backgroundColor = [UIColor clearColor]; + + self.contentInsets = UIEdgeInsetsMake(20.0f, 20.0f, 20.0f, 20.0f); + self.marginInsets = UIEdgeInsetsMake(20.0f, 20.0f, 20.0f, 20.0f); + + self.layoutChangeAnimationDuration = 0.3; + + [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]]; + + _indicatorView = [[JGProgressHUDIndeterminateIndicatorView alloc] initWithHUDStyle:self.style]; + + _cornerRadius = 10.0f; + } + + return self; +} + ++ (instancetype)progressHUDWithStyle:(JGProgressHUDStyle)style { + return [(JGProgressHUD *)[self alloc] initWithStyle:style]; +} + +#pragma mark - Layout + +- (void)setHUDViewFrameCenterWithSize:(CGSize)size { + CGRect frame = CGRectZero; + + frame.size = size; + + CGRect viewBounds = self.bounds; + + CGPoint center = CGPointMake(viewBounds.origin.x+viewBounds.size.width/2.0f, viewBounds.origin.y+viewBounds.size.height/2.0f); + + switch (self.position) { + case JGProgressHUDPositionTopLeft: + frame.origin.x = self.marginInsets.left; + frame.origin.y = self.marginInsets.top; + break; + + case JGProgressHUDPositionTopCenter: + frame.origin.x = center.x-frame.size.width/2.0f; + frame.origin.y = self.marginInsets.top; + break; + + case JGProgressHUDPositionTopRight: + frame.origin.x = viewBounds.size.width-self.marginInsets.right-frame.size.width; + frame.origin.y = self.marginInsets.top; + break; + + case JGProgressHUDPositionCenterLeft: + frame.origin.x = self.marginInsets.left; + frame.origin.y = center.y-frame.size.height/2.0f; + break; + + case JGProgressHUDPositionCenter: + frame.origin.x = center.x-frame.size.width/2.0f; + frame.origin.y = center.y-frame.size.height/2.0f; + break; + + case JGProgressHUDPositionCenterRight: + frame.origin.x = viewBounds.size.width-self.marginInsets.right-frame.size.width; + frame.origin.y = center.y-frame.size.height/2.0f; + break; + + case JGProgressHUDPositionBottomLeft: + frame.origin.x = self.marginInsets.left; + frame.origin.y = viewBounds.size.height-self.marginInsets.bottom-frame.size.height; + break; + + case JGProgressHUDPositionBottomCenter: + frame.origin.x = center.x-frame.size.width/2.0f; + frame.origin.y = viewBounds.size.height-self.marginInsets.bottom-frame.size.height; + break; + + case JGProgressHUDPositionBottomRight: + frame.origin.x = viewBounds.size.width-self.marginInsets.right-frame.size.width; + frame.origin.y = viewBounds.size.height-self.marginInsets.bottom-frame.size.height; + break; + } + + self.HUDView.frame = frame; +} + +- (void)updateHUDAnimated:(BOOL)animated animateIndicatorViewFrame:(BOOL)animateIndicator { + if (_transitioning) { + _updateAfterAppear = YES; + return; + } + + if (!self.superview) { + return; + } + + CGRect indicatorFrame = self.indicatorView.frame; + indicatorFrame.origin.y = self.contentInsets.top; + + CGFloat maxContentWidth = self.frame.size.width-self.marginInsets.left-self.marginInsets.right-self.contentInsets.left-self.contentInsets.right; + CGFloat maxContentHeight = self.frame.size.height-self.marginInsets.top-self.marginInsets.bottom-self.contentInsets.top-self.contentInsets.bottom; + + CGSize maxContentSize = (CGSize){maxContentWidth, maxContentHeight}; + + //Label size + CGRect labelFrame = CGRectZero; + CGRect detailFrame = CGRectZero; + + if (_textLabel) { + if (iOS7) { + NSDictionary *attributes = @{NSFontAttributeName : self.textLabel.font}; + labelFrame.size = [self.textLabel.text boundingRectWithSize:maxContentSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; + } + else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + labelFrame.size = [self.textLabel.text sizeWithFont:self.textLabel.font constrainedToSize:maxContentSize lineBreakMode:self.textLabel.lineBreakMode]; +#pragma clang diagnostic pop + } + + labelFrame.origin.y = CGRectGetMaxY(indicatorFrame); + + if (!CGRectIsEmpty(labelFrame) && !CGRectIsEmpty(indicatorFrame)) { + labelFrame.origin.y += 10.0f; + } + } + + if (_detailTextLabel) { + if (iOS7) { + NSDictionary *attributes = @{NSFontAttributeName : self.detailTextLabel.font}; + detailFrame.size = [self.detailTextLabel.text boundingRectWithSize:maxContentSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; + } + else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + detailFrame.size = [self.detailTextLabel.text sizeWithFont:self.detailTextLabel.font constrainedToSize:maxContentSize lineBreakMode:self.detailTextLabel.lineBreakMode]; +#pragma clang diagnostic pop + } + + detailFrame.origin.y = CGRectGetMaxY(labelFrame)+5.0f; + + if (!CGRectIsEmpty(detailFrame) && !CGRectIsEmpty(indicatorFrame) && CGRectIsEmpty(labelFrame)) { + detailFrame.origin.y += 5.0f; + } + } + + //HUD size + CGSize size = CGSizeZero; + + CGFloat width = MIN(self.contentInsets.left+MAX(indicatorFrame.size.width, MAX(labelFrame.size.width, detailFrame.size.width))+self.contentInsets.right, self.frame.size.width-self.marginInsets.left-self.marginInsets.right); + + CGFloat height = MAX(CGRectGetMaxY(labelFrame), MAX(CGRectGetMaxY(detailFrame), CGRectGetMaxY(indicatorFrame)))+self.contentInsets.bottom; + + if (self.square) { + CGFloat uniSize = MAX(width, height); + + size.width = uniSize; + size.height = uniSize; + + CGFloat heightDelta = (uniSize-height)/2.0f; + + labelFrame.origin.y += heightDelta; + detailFrame.origin.y += heightDelta; + indicatorFrame.origin.y += heightDelta; + } + else { + size.width = width; + size.height = height; + } + + CGPoint center = CGPointMake(size.width/2.0f, size.height/2.0f); + + indicatorFrame.origin.x = center.x-indicatorFrame.size.width/2.0f; + labelFrame.origin.x = center.x-labelFrame.size.width/2.0f; + detailFrame.origin.x = center.x-detailFrame.size.width/2.0f; + + void (^updates)(void) = ^{ + [self setHUDViewFrameCenterWithSize:size]; + + if (animateIndicator) { + self.indicatorView.frame = indicatorFrame; + } + + _textLabel.frame = labelFrame; + _detailTextLabel.frame = detailFrame; + }; + + if (!animateIndicator) { + self.indicatorView.frame = indicatorFrame; + } + + if (self.layoutChangeAnimationDuration > 0.0f && animated && !_transitioning) { + [UIView animateWithDuration:self.layoutChangeAnimationDuration delay:0.0 options:UIViewAnimationOptionAllowAnimatedContent | UIViewAnimationOptionCurveEaseInOut animations:updates completion:nil]; + } + else { + updates(); + } +} + +- (CGRect)fullFrameInView:(UIView *)view { + CGRect _keyboardFrame = [view convertRect:[[self class] currentKeyboardFrame] fromView:nil]; + CGRect frame = view.bounds; + + if (!CGRectIsEmpty(_keyboardFrame) && CGRectIntersectsRect(frame, _keyboardFrame)) { + frame.size.height = MIN(frame.size.height, CGRectGetMinY(_keyboardFrame)); + } + + return frame; +} + +- (void)applyCornerRadius { + self.HUDView.layer.cornerRadius = self.cornerRadius; + if (iOS8) { + for (UIView *sub in self.HUDView.subviews) { + sub.layer.cornerRadius = self.cornerRadius; + } + }; +} + +#pragma mark - Showing + +- (void)cleanUpAfterPresentation { + self.hidden = NO; + + _transitioning = NO; + + if (_indicatorViewAfterTransitioning) { + self.indicatorView = _indicatorViewAfterTransitioning; + _indicatorViewAfterTransitioning = nil; + _updateAfterAppear = NO; + } + else if (_updateAfterAppear) { + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; + _updateAfterAppear = NO; + } + + if ([self.delegate respondsToSelector:@selector(progressHUD:didPresentInView:)]){ + [self.delegate progressHUD:self didPresentInView:self.targetView]; + } + + if (_dismissAfterTransitionFinished) { + [self dismissAnimated:_dismissAfterTransitionFinishedWithAnimation]; + _dismissAfterTransitionFinished = NO; + _dismissAfterTransitionFinishedWithAnimation = NO; + } +} + +- (void)showInView:(UIView *)view { + [self showInView:view animated:YES]; +} + +- (void)showInView:(UIView *)view animated:(BOOL)animated { + CGRect frame = [self fullFrameInView:view]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChanged:) name:UIKeyboardWillChangeFrameNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChanged:) name:UIKeyboardDidChangeFrameNotification object:nil]; + + [self showInRect:frame inView:view animated:animated]; +} + +- (void)showInRect:(CGRect)rect inView:(UIView *)view { + [self showInRect:rect inView:view animated:YES]; +} + +- (void)showInRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated { + if (_transitioning) { + return; + } + else if (self.targetView != nil) { +#if DEBUG + NSLog(@"[Warning] The HUD is already visible! Ignoring."); +#endif + return; + } + + _targetView = view; + + self.frame = rect; + [view addSubview:self]; + + [self updateHUDAnimated:NO animateIndicatorViewFrame:YES]; + + _transitioning = YES; + + if ([self.delegate respondsToSelector:@selector(progressHUD:willPresentInView:)]) { + [self.delegate progressHUD:self willPresentInView:view]; + } + + if (animated && self.animation) { + [self.animation show]; + } + else { + [self cleanUpAfterPresentation]; + } +} + +#pragma mark - Hiding + +- (void)cleanUpAfterDismissal { + self.hidden = YES; + [self removeFromSuperview]; + + [self removeObservers]; + + _transitioning = NO; + _dismissAfterTransitionFinished = NO; + _dismissAfterTransitionFinishedWithAnimation = NO; + + if ([self.delegate respondsToSelector:@selector(progressHUD:didDismissFromView:)]) { + [self.delegate progressHUD:self didDismissFromView:self.targetView]; + } + + _targetView = nil; +} + +- (void)dismiss { + [self dismissAnimated:YES]; +} + +- (void)dismissAnimated:(BOOL)animated { + if (_transitioning) { + _dismissAfterTransitionFinished = YES; + _dismissAfterTransitionFinishedWithAnimation = animated; + return; + } + + if (self.targetView == nil) { + return; + } + + _transitioning = YES; + + if ([self.delegate respondsToSelector:@selector(progressHUD:willDismissFromView:)]) { + [self.delegate progressHUD:self willDismissFromView:self.targetView]; + } + + if (animated && self.animation) { + [self.animation hide]; + } + else { + [self cleanUpAfterDismissal]; + } +} + +- (void)dismissAfterDelay:(NSTimeInterval)delay { + [self dismissAfterDelay:delay animated:YES]; +} + +- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated { + __weak __typeof(self) weakSelf = self; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (weakSelf) { + __strong __typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf.visible) { + [strongSelf dismissAnimated:animated]; + } + } + }); +} + +#pragma mark - Callbacks + +- (void)tapped:(UITapGestureRecognizer *)t { + if (CGRectContainsPoint(self.contentView.bounds, [t locationInView:self.contentView])) { + if (self.tapOnHUDViewBlock) { + self.tapOnHUDViewBlock(self); + } + } + else if (self.tapOutsideBlock) { + self.tapOutsideBlock(self); + } +} + +- (void)keyboardFrameChanged:(NSNotification *)notification { + CGRect frame = [self fullFrameInView:self.targetView]; + + if (CGRectEqualToRect(self.frame, frame)) { + return; + } + + NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; + + [UIView beginAnimations:@"de.j-gessner.jgprogresshud.keyboardframechange" context:NULL]; + [UIView setAnimationCurve:curve]; + [UIView setAnimationBeginsFromCurrentState:YES]; + [UIView setAnimationDuration:duration]; + + self.frame = frame; + [self updateHUDAnimated:NO animateIndicatorViewFrame:YES]; + + [UIView commitAnimations]; +} + +- (void)orientationChanged { + if (self.targetView && !CGRectEqualToRect(self.bounds, self.targetView.bounds)) { + [UIView animateWithDuration:(iPad ? 0.4 : 0.3) delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ + self.frame = [self fullFrameInView:self.targetView]; + [self updateHUDAnimated:NO animateIndicatorViewFrame:YES]; + } completion:nil]; + } +} + +- (void)animationDidFinish:(BOOL)presenting { + if (presenting) { + [self cleanUpAfterPresentation]; + } + else { + [self cleanUpAfterDismissal]; + } +} + +#pragma mark - Getters + +- (BOOL)isVisible { + return (self.superview != nil); +} + +- (UIView *)HUDView { + if (!_HUDView) { + if (iOS8) { + UIBlurEffectStyle effect = 0; + + if (self.style == JGProgressHUDStyleDark) { + effect = UIBlurEffectStyleDark; + } + else if (self.style == JGProgressHUDStyleLight) { + effect = UIBlurEffectStyleLight; + } + else { + effect = UIBlurEffectStyleExtraLight; + } + + UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:effect]; + + _HUDView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; + } + else { + _HUDView = [[UIView alloc] init]; + + if (self.style == JGProgressHUDStyleDark) { + _HUDView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.8f]; + } + else if (self.style == JGProgressHUDStyleLight) { + _HUDView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.75f]; + } + else { + _HUDView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.95f]; + } + } + + if (iOS7) { + UIInterpolatingMotionEffect *x = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + + CGFloat maxMovement = 20.0f; + + x.minimumRelativeValue = @(-maxMovement); + x.maximumRelativeValue = @(maxMovement); + + UIInterpolatingMotionEffect *y = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + + y.minimumRelativeValue = @(-maxMovement); + y.maximumRelativeValue = @(maxMovement); + + _HUDView.motionEffects = @[x, y]; + } + + [self applyCornerRadius]; + + [self addSubview:_HUDView]; + + if (self.indicatorView) { + [self.contentView addSubview:self.indicatorView]; + } + + [self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]]; + } + + return _HUDView; +} + +- (UIView *)contentView { + if (iOS8) { + return ((UIVisualEffectView *)self.HUDView).contentView; + } + else { + return self.HUDView; + } +} + +- (UILabel *)textLabel { + if (!_textLabel) { + _textLabel = [[UILabel alloc] init]; + _textLabel.backgroundColor = [UIColor clearColor]; + _textLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]); + _textLabel.textAlignment = NSTextAlignmentCenter; + _textLabel.font = [UIFont boldSystemFontOfSize:15.0f]; + _textLabel.numberOfLines = 0; + [_textLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptions)kNilOptions context:NULL]; + [_textLabel addObserver:self forKeyPath:@"font" options:(NSKeyValueObservingOptions)kNilOptions context:NULL]; + + [self.contentView addSubview:_textLabel]; + } + + return _textLabel; +} + +- (UILabel *)detailTextLabel { + if (!_detailTextLabel) { + _detailTextLabel = [[UILabel alloc] init]; + _detailTextLabel.backgroundColor = [UIColor clearColor]; + _detailTextLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]); + _detailTextLabel.textAlignment = NSTextAlignmentCenter; + _detailTextLabel.font = [UIFont systemFontOfSize:13.0f]; + _detailTextLabel.numberOfLines = 0; + [_detailTextLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptions)kNilOptions context:NULL]; + [_detailTextLabel addObserver:self forKeyPath:@"font" options:(NSKeyValueObservingOptions)kNilOptions context:NULL]; + + [self.contentView addSubview:_detailTextLabel]; + } + + return _detailTextLabel; +} + +- (JGProgressHUDAnimation *)animation { + if (!_animation) { + self.animation = [JGProgressHUDFadeAnimation animation]; + } + + return _animation; +} + +#pragma mark - Setters + +- (void)setCornerRadius:(CGFloat)cornerRadius { + if (fequal(self.cornerRadius, cornerRadius)) { + return; + } + + _cornerRadius = cornerRadius; + + [self applyCornerRadius]; +} + +- (void)setAnimation:(JGProgressHUDAnimation *)animation { + if (_animation == animation) { + return; + } + + _animation.progressHUD = nil; + + _animation = animation; + + _animation.progressHUD = self; +} + +- (void)setPosition:(JGProgressHUDPosition)position { + if (self.position == position) { + return; + } + + _position = position; + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; +} + +- (void)setSquare:(BOOL)square { + if (self.square == square) { + return; + } + + _square = square; + + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; +} + +- (void)setIndicatorView:(JGProgressHUDIndicatorView *)indicatorView { + if (self.indicatorView == indicatorView) { + return; + } + + if (_transitioning) { + _indicatorViewAfterTransitioning = indicatorView; + return; + } + + [_indicatorView removeFromSuperview]; + _indicatorView = indicatorView; + + if (self.indicatorView) { + [self.contentView addSubview:self.indicatorView]; + } + + [self updateHUDAnimated:YES animateIndicatorViewFrame:NO]; +} + +- (void)setMarginInsets:(UIEdgeInsets)marginInsets { + if (UIEdgeInsetsEqualToEdgeInsets(self.marginInsets, marginInsets)) { + return; + } + + _marginInsets = marginInsets; + + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; +} + +- (void)setContentInsets:(UIEdgeInsets)contentInsets { + if (UIEdgeInsetsEqualToEdgeInsets(self.contentInsets, contentInsets)) { + return; + } + + _contentInsets = contentInsets; + + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; +} + +- (void)setProgress:(float)progress { + [self setProgress:progress animated:NO]; +} + +- (void)setProgress:(float)progress animated:(BOOL)animated { + if (fequal(self.progress, progress)) { + return; + } + + _progress = progress; + + [self.indicatorView setProgress:progress animated:animated]; +} + +#pragma mark - Overrides + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + if (self.interactionType == JGProgressHUDInteractionTypeBlockNoTouches) { + return nil; + } + else { + UIView *view = [super hitTest:point withEvent:event]; + + if (self.interactionType == JGProgressHUDInteractionTypeBlockAllTouches) { + return view; + } + else if (self.interactionType == JGProgressHUDInteractionTypeBlockTouchesOnHUDView && view != self) { + return view; + } + + return nil; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == _textLabel || object == _detailTextLabel) { + [self updateHUDAnimated:YES animateIndicatorViewFrame:YES]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)dealloc { + [self removeObservers]; + + [_textLabel removeObserver:self forKeyPath:@"text"]; + [_textLabel removeObserver:self forKeyPath:@"font"]; + + [_detailTextLabel removeObserver:self forKeyPath:@"text"]; + [_detailTextLabel removeObserver:self forKeyPath:@"font"]; +} + +- (void)removeObservers { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidChangeFrameNotification object:nil]; +} + + +@end + +@implementation JGProgressHUD (HUDManagement) + + ++ (NSArray *)allProgressHUDsInView:(UIView *)view { + NSMutableArray *HUDs = [NSMutableArray array]; + + for (UIView *v in view.subviews) { + if ([v isKindOfClass:[JGProgressHUD class]]) { + [HUDs addObject:v]; + } + } + + return HUDs.copy; +} + + ++ (NSMutableArray *)_allProgressHUDsInViewHierarchy:(UIView *)view { + NSMutableArray *HUDs = [NSMutableArray array]; + + for (UIView *v in view.subviews) { + if ([v isKindOfClass:[JGProgressHUD class]]) { + [HUDs addObject:v]; + } + else { + [HUDs addObjectsFromArray:[self _allProgressHUDsInViewHierarchy:v]]; + } + } + + return HUDs; +} + ++ (NSArray *)allProgressHUDsInViewHierarchy:(UIView *)view { + return [self _allProgressHUDsInViewHierarchy:view].copy; +} + +@end + + +@implementation JGProgressHUD (Deprecated) + +@dynamic progressIndicatorView, useProgressIndicatorView; + +- (void)setProgressIndicatorView:(JGProgressHUDIndicatorView *)progressIndicatorView { + [self setIndicatorView:progressIndicatorView]; +} + +- (JGProgressHUDIndicatorView *)progressIndicatorView { + return [self indicatorView]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDAnimation.h b/Anyway/JGProgressHUD/JGProgressHUDAnimation.h new file mode 100755 index 0000000..94e273f --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDAnimation.h @@ -0,0 +1,49 @@ +// +// JGProgressHUDAnimation.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import +#import + +@class JGProgressHUD; + +/** + You may subclass this class to create a custom progress indicator view. + */ +@interface JGProgressHUDAnimation : NSObject + +/** + Convenience method for initializing an animation. + */ ++ (instancetype)animation; + + +/** + The HUD which uses this animation. + */ +@property (nonatomic, weak, readonly) JGProgressHUD *progressHUD; + + +/** + The @c progressHUD is hidden from screen with @c alpha = 1 and @c hidden = @c YES. Ideally, you should prepare the HUD for presentation, then set @c hidden to @c NO on the @c progressHUD and then perform the animation. + @post Call @c animationFinished. + */ +- (void)show NS_REQUIRES_SUPER; + +/** + The @c progressHUD wis visible on screen with @c alpha = 1 and @c hidden = @c NO. You should only perform the animation in this method, the @c progressHUD itself will take care of hiding itself and removing itself from superview. + @post Call @c animationFinished. + */ +- (void)hide NS_REQUIRES_SUPER; + +/** + @pre This method should only be called at the end of a @c show or @c hide animaiton. + @attention ALWAYS call this method after completing a @c show or @c hide animation. + */ +- (void)animationFinished NS_REQUIRES_SUPER; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDAnimation.m b/Anyway/JGProgressHUD/JGProgressHUDAnimation.m new file mode 100755 index 0000000..fb2628f --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDAnimation.m @@ -0,0 +1,48 @@ +// +// JGProgressHUDAnimation.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDAnimation.h" +#import "JGProgressHUD.h" + +@interface JGProgressHUD (Private) + +- (void)animationDidFinish:(BOOL)presenting; + +@end + +@interface JGProgressHUDAnimation () { + BOOL _presenting; +} + +@property (nonatomic, weak) JGProgressHUD *progressHUD; + +@end + +@implementation JGProgressHUDAnimation + +#pragma mark - Initializers + ++ (instancetype)animation { + return [[self alloc] init]; +} + +#pragma mark - Public methods + +- (void)show { + _presenting = YES; +} + +- (void)hide { + _presenting = NO; +} + +- (void)animationFinished { + [self.progressHUD animationDidFinish:_presenting]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.h new file mode 100755 index 0000000..db60a0d --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.h @@ -0,0 +1,18 @@ +// +// JGProgressHUDErrorIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 19.08.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDIndicatorView.h" + +@interface JGProgressHUDErrorIndicatorView : JGProgressHUDIndicatorView + +/** + Default initializer for this class. + */ +- (instancetype)init; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.m new file mode 100755 index 0000000..b1ad471 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDErrorIndicatorView.m @@ -0,0 +1,27 @@ +// +// JGProgressHUDErrorIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 19.08.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDErrorIndicatorView.h" + +@implementation JGProgressHUDErrorIndicatorView + +- (instancetype)initWithContentView:(UIView *__unused)contentView { + NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"JGProgressHUD Resources" ofType:@"bundle"]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[bundlePath stringByAppendingPathComponent:@"jg_hud_error.png"]]]; + + self = [super initWithContentView:imageView]; + + return self; +} + +- (instancetype)init { + return [self initWithContentView:nil]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.h b/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.h new file mode 100755 index 0000000..1f05b37 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.h @@ -0,0 +1,27 @@ +// +// JGProgressHUDFadeAnimation.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDAnimation.h" + +@interface JGProgressHUDFadeAnimation : JGProgressHUDAnimation + +/** + Duration of the animation. + + @b Default: 0.4. + */ +@property (nonatomic, assign) NSTimeInterval duration; + +/** + Animation options + + @b Default: UIViewAnimationOptionCurveEaseInOut. + */ +@property (nonatomic, assign) UIViewAnimationOptions animationOptions; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.m b/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.m new file mode 100755 index 0000000..b1f4119 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDFadeAnimation.m @@ -0,0 +1,61 @@ +// +// JGProgressHUDFadeAnimation.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDFadeAnimation.h" +#import "JGProgressHUD.h" + +@implementation JGProgressHUDFadeAnimation + +#pragma mark - Initializers + +- (instancetype)init { + self = [super init]; + if (self) { + self.duration = 0.4; + self.animationOptions = UIViewAnimationOptionCurveEaseInOut; + } + return self; +} + +- (void)setAnimationOptions:(UIViewAnimationOptions)animationOptions { + _animationOptions = (animationOptions | UIViewAnimationOptionBeginFromCurrentState); +} + +#pragma mark - Showing + +- (void)show { + [super show]; + + //Prepare animation + self.progressHUD.alpha = 0.0f; + + //Now unhide HUD + self.progressHUD.hidden = NO; + + //Perform the presentation animation + [UIView animateWithDuration:self.duration delay:0.0 options:self.animationOptions animations:^{ + self.progressHUD.alpha = 1.0f; + } completion:^(BOOL __unused finished) { + [self animationFinished]; + }]; +} + +#pragma mark - Hiding + +- (void)hide { + [super hide]; + + //Perform the dismissal animation + [UIView animateWithDuration:self.duration delay:0.0 options:self.animationOptions animations:^{ + self.progressHUD.alpha = 0.0f; + } completion:^(BOOL __unused finished) { + [self animationFinished]; + }]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.h b/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.h new file mode 100755 index 0000000..8560177 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.h @@ -0,0 +1,34 @@ +// +// JGProgressHUDFadeZoomAnimation.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDAnimation.h" + +@interface JGProgressHUDFadeZoomAnimation : JGProgressHUDAnimation + +/** + Duration of the animation from or to the shrinked state. + + @b Default: 0.2. + */ +@property (nonatomic, assign) NSTimeInterval shrinkAnimationDuaration; + +/** + Duration of the animation from or to the expanded state. + + @b Default: 0.1. + */ +@property (nonatomic, assign) NSTimeInterval expandAnimationDuaration; + +/** + The scale to apply to the HUD when expanding. + + @b Default: (1.1f, 1.1f). + */ +@property (nonatomic, assign) CGSize expandScale; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.m b/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.m new file mode 100755 index 0000000..a2da64b --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDFadeZoomAnimation.m @@ -0,0 +1,84 @@ +// +// JGProgressHUDFadeZoomAnimation.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDFadeZoomAnimation.h" +#import "JGProgressHUD.h" + +@implementation JGProgressHUDFadeZoomAnimation + +#pragma mark - Initializers + +- (instancetype)init { + self = [super init]; + if (self) { + self.shrinkAnimationDuaration = 0.2; + self.expandAnimationDuaration = 0.1; + self.expandScale = CGSizeMake(1.1f, 1.1f); + } + return self; +} + +#pragma mark - Showing + +- (void)show { + [super show]; + + //Prepare the HUD + self.progressHUD.alpha = 0.0f; + self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(0.1f, 0.1f); + + NSTimeInterval totalDuration = self.expandAnimationDuaration+self.shrinkAnimationDuaration; + + //Show the HUD + self.progressHUD.hidden = NO; + + + //Now animate the presentation + [UIView animateWithDuration:totalDuration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut) animations:^{ + self.progressHUD.alpha = 1.0f; + } completion:nil]; + + [UIView animateWithDuration:self.shrinkAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{ + self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(self.expandScale.width, self.expandScale.height); + } completion:^(BOOL __unused _finished) { + [UIView animateWithDuration:self.expandAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{ + self.progressHUD.HUDView.transform = CGAffineTransformIdentity; + } completion:^(BOOL __unused __finished) { + [self animationFinished]; + }]; + }]; +} + +#pragma mark - Hiding + +- (void)hide { + [super hide]; + + NSTimeInterval totalDuration = self.expandAnimationDuaration+self.shrinkAnimationDuaration; + + //Animate the dismissal + [UIView animateWithDuration:totalDuration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut) animations:^{ + self.progressHUD.alpha = 0.0f; + } completion:nil]; + + [UIView animateWithDuration:self.expandAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{ + self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(self.expandScale.width, self.expandScale.height); + } completion:^(BOOL __unused _finished) { + [UIView animateWithDuration:self.shrinkAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{ + self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(0.1f, 0.1f); + } completion:^(BOOL __unused __finished) { + //HUD is now hidden, restore the transform + self.progressHUD.HUDView.transform = CGAffineTransformIdentity; + + //Alway call this last + [self animationFinished]; + }]; + }]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.h new file mode 100755 index 0000000..1e37a7d --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.h @@ -0,0 +1,25 @@ +// +// JGProgressHUDIndeterminateIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 19.07.14. +// Copyright (c) 2014 Hardtack. All rights reserved. +// + +#import "JGProgressHUD.h" +#import "JGProgressHUDIndicatorView.h" + +@interface JGProgressHUDIndeterminateIndicatorView : JGProgressHUDIndicatorView + +/** + Initializes the indicator view and sets the correct color to match the HUD style. + */ +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style; + +/** + Set the color of the activity indicator view. + @param color The color to apply to the activity indicator view. + */ +- (void)setColor:(UIColor *)color; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.m new file mode 100755 index 0000000..da224f3 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.m @@ -0,0 +1,35 @@ +// +// JGProgressHUDIndeterminateIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 19.07.14. +// Copyright (c) 2014 Hardtack. All rights reserved. +// + +#import "JGProgressHUDIndeterminateIndicatorView.h" + +@implementation JGProgressHUDIndeterminateIndicatorView + +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style { + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + [activityIndicatorView startAnimating]; + self = [super initWithContentView:activityIndicatorView]; + + if (self) { + if (style != JGProgressHUDStyleDark) { + self.color = [UIColor blackColor]; + } + } + + return self; +} + +- (instancetype)init { + return [self initWithHUDStyle:0]; +} + +- (void)setColor:(UIColor *)color { + [(UIActivityIndicatorView *)self.contentView setColor:color]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.h new file mode 100755 index 0000000..83b47c6 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.h @@ -0,0 +1,43 @@ +// +// JGProgressHUDIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import +#import + +/** + You may subclass this class to create a custom progress indicator view. + */ +@interface JGProgressHUDIndicatorView : UIView + +/** + Designated initializer for this class. + @param contentView The content view to place on the container view (the container is the JGProgressHUDIndicatorView). + */ +- (instancetype)initWithContentView:(UIView *)contentView; + +/** + The content view which displays the progress. + */ +@property (nonatomic, strong, readonly) UIView *contentView; + +/** + Ranges from 0.0 to 1.0. + */ +@property (nonatomic, assign) float progress; + +/** + Adjusts the current progress shown by the receiver, optionally animating the change. + + The current progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. Values less than 0.0 and greater than 1.0 are pinned to those limits. + + @param progress The new progress value. + @param animated YES if the change should be animated, NO if the change should happen immediately. + */ +- (void)setProgress:(float)progress animated:(BOOL)animated; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.m new file mode 100755 index 0000000..abaa566 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDIndicatorView.m @@ -0,0 +1,53 @@ +// +// JGProgressHUDIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDIndicatorView.h" +#import "JGProgressHUD.h" + +@implementation JGProgressHUDIndicatorView + +#pragma mark - Initializers + +- (instancetype)initWithFrame:(CGRect __unused)frame { + return [self init]; +} + +- (instancetype)init { + return [self initWithContentView:nil]; +} + +- (instancetype)initWithContentView:(UIView *)contentView { + self = [super initWithFrame:(contentView ? contentView.frame : CGRectMake(0.0f, 0.0f, 50.0f, 50.0f))]; + if (self) { + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + + if (contentView) { + _contentView = contentView; + + [self addSubview:self.contentView]; + } + } + return self; +} + +#pragma mark - Getters & Setters + +- (void)setProgress:(float)progress { + [self setProgress:progress animated:NO]; +} + +- (void)setProgress:(float)progress animated:(BOOL __unused)animated { + if (fequal(self.progress, progress)) { + return; + } + + _progress = progress; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.h new file mode 100755 index 0000000..fc8e775 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.h @@ -0,0 +1,33 @@ +// +// JGProgressHUDPieIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 19.07.14. +// Copyright (c) 2014 Hardtack. All rights reserved. +// + +#import "JGProgressHUD.h" +#import "JGProgressHUDIndicatorView.h" + +@interface JGProgressHUDPieIndicatorView : JGProgressHUDIndicatorView + +/** + Initializes the indicator view and sets the correct color to match the HUD style. + */ +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style; + +/** + Tint color of the Pie. + + @b Default: White for JGProgressHUDStyleDark, otherwise black. + */ +@property (nonatomic, strong) UIColor *color; + +/** + The background fill color inside the pie. + + @b Default: Dark gray for JGProgressHUDStyleDark, otherwise light gray. + */ +@property (nonatomic, strong) UIColor *fillColor; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.m new file mode 100755 index 0000000..ae0973f --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDPieIndicatorView.m @@ -0,0 +1,173 @@ +// +// JGProgressHUDPieIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 19.07.14. +// Copyright (c) 2014 Hardtack. All rights reserved. +// + +#import "JGProgressHUDPieIndicatorView.h" + +@interface JGProgressHUDPieIndicatorLayer : CALayer + +@property (nonatomic, assign) float progress; + +@property (nonatomic, weak) UIColor *color; + +@property (nonatomic, weak) UIColor *fillColor; + +@end + +@implementation JGProgressHUDPieIndicatorLayer + +@dynamic progress, color, fillColor; + ++ (BOOL)needsDisplayForKey:(NSString *)key { + return ([key isEqualToString:@"progress"] || [key isEqualToString:@"color"] || [key isEqualToString:@"fillColor"] || [super needsDisplayForKey:key]); +} + +- (id )actionForKey:(NSString *)key { + if ([key isEqualToString:@"progress"]) { + CABasicAnimation *progressAnimation = [CABasicAnimation animation]; + progressAnimation.fromValue = [self.presentationLayer valueForKey:key]; + + progressAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + return progressAnimation; + } + + return [super actionForKey:key]; +} + +- (void)drawInContext:(CGContextRef)ctx { + UIGraphicsPushContext(ctx); + + CGRect rect = self.bounds; + + CGPoint center = CGPointMake(rect.origin.x + (CGFloat)floor(rect.size.height/2.0f), rect.origin.y + (CGFloat)floor(rect.size.height/2.0f)); + CGFloat lineWidth = 2.0f; + CGFloat radius = (CGFloat)floor(MIN(rect.size.width, rect.size.height)/2.0f)-lineWidth; + + //Border && Fill + UIBezierPath *borderPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0.0f endAngle:2.0f*(CGFloat)M_PI clockwise:NO]; + + [borderPath setLineWidth:lineWidth]; + + if (self.fillColor) { + [self.fillColor setFill]; + + [borderPath fill]; + } + + [self.color set]; + + [borderPath stroke]; + + + //Progress + if (self.progress) { + UIBezierPath *processPath = [UIBezierPath bezierPath]; + + [processPath setLineWidth:radius]; + + CGFloat startAngle = -((CGFloat)M_PI/2.0f); + CGFloat endAngle = startAngle + 2.0f * (CGFloat)M_PI * self.progress; + + [processPath addArcWithCenter:center radius:radius/2.0f startAngle:startAngle endAngle:endAngle clockwise:YES]; + + [processPath stroke]; + + UIGraphicsPopContext(); + } +} + +@end + + +@implementation JGProgressHUDPieIndicatorView + +#pragma mark - Initializers + +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style { + self = [super init]; + + if (self) { + self.layer.contentsScale = [UIScreen mainScreen].scale; + [self.layer setNeedsDisplay]; + + if (style == JGProgressHUDStyleDark) { + self.color = [UIColor whiteColor]; + self.fillColor = [UIColor colorWithWhite:0.2f alpha:1.0f]; + } + else { + self.color = [UIColor blackColor]; + if (style == JGProgressHUDStyleLight) { + self.fillColor = [UIColor colorWithWhite:0.85f alpha:1.0f]; + } + else { + self.fillColor = [UIColor colorWithWhite:0.9f alpha:1.0f]; + } + } + } + + return self; +} + +- (instancetype)initWithContentView:(UIView *)contentView { + self = [super initWithContentView:contentView]; + + if (self) { + self.layer.contentsScale = [UIScreen mainScreen].scale; + [self.layer setNeedsDisplay]; + + self.color = [UIColor whiteColor]; + } + + return self; +} + +#pragma mark - Getters & Setters + +- (void)setColor:(UIColor *)tintColor { + if ([tintColor isEqual:self.color]) { + return; + } + + _color = tintColor; + + [(JGProgressHUDPieIndicatorLayer *)self.layer setColor:self.color]; +} + +- (void)setFillColor:(UIColor *)fillColor { + if ([fillColor isEqual:self.fillColor]) { + return; + } + + _fillColor = fillColor; + + [(JGProgressHUDPieIndicatorLayer *)self.layer setFillColor:self.fillColor]; +} + +- (void)setProgress:(float)progress animated:(BOOL)animated { + if (fequal(self.progress, progress)) { + return; + } + + [super setProgress:progress animated:animated]; + + [CATransaction begin]; + + [CATransaction setAnimationDuration:(animated ? 0.3 : 0.0)]; + + [(JGProgressHUDPieIndicatorLayer *)self.layer setProgress:progress]; + + [CATransaction commit]; +} + +#pragma mark - Overrides + ++ (Class)layerClass { + return [JGProgressHUDPieIndicatorLayer class]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.h new file mode 100755 index 0000000..7351f97 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.h @@ -0,0 +1,47 @@ +// +// JGProgressHUDRingIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUD.h" +#import "JGProgressHUDIndicatorView.h" + +@interface JGProgressHUDRingIndicatorView : JGProgressHUDIndicatorView + +/** + Initializes the indicator view and sets the correct color to match the HUD style. + */ +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style; + +/** + Background color of the ring. + + @b Default: Black for JGProgressHUDStyleDark, light gray otherwise. + */ +@property (nonatomic, strong) UIColor *ringBackgroundColor; + +/** + Progress color of the progress ring. + + @b Default: White for JGProgressHUDStyleDark, otherwise black. + */ +@property (nonatomic, strong) UIColor *ringColor; + +/** + Sets if the progress ring should have rounded corners. + + @b Default: NO. + */ +@property (nonatomic, assign) BOOL roundProgressLine; + +/** + Width of the ring. + + @b Default: 3.0. + */ +@property (nonatomic, assign) CGFloat ringWidth; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.m new file mode 100755 index 0000000..af68a4d --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDRingIndicatorView.m @@ -0,0 +1,189 @@ +// +// JGProgressHUDRingIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 20.7.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDRingIndicatorView.h" + + +@interface JGProgressHUDRingIndicatorLayer : CALayer + +@property (nonatomic, assign) float progress; + +@property (nonatomic, weak) UIColor *ringColor; +@property (nonatomic, weak) UIColor *ringBackgroundColor; + +@property (nonatomic, assign) BOOL roundProgressLine; + +@property (nonatomic, assign) CGFloat ringWidth; + +@end + +@implementation JGProgressHUDRingIndicatorLayer + +@dynamic progress, ringBackgroundColor, ringColor, ringWidth, roundProgressLine; + ++ (BOOL)needsDisplayForKey:(NSString *)key { + return ([key isEqualToString:@"progress"] || [key isEqualToString:@"ringColor"] || [key isEqualToString:@"ringBackgroundColor"] || [key isEqualToString:@"roundProgressLine"] || [key isEqualToString:@"ringWidth"] || [super needsDisplayForKey:key]); +} + +- (id )actionForKey:(NSString *)key { + if ([key isEqualToString:@"progress"]) { + CABasicAnimation *progressAnimation = [CABasicAnimation animation]; + progressAnimation.fromValue = [self.presentationLayer valueForKey:key]; + + progressAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + return progressAnimation; + } + + return [super actionForKey:key]; +} + +- (void)drawInContext:(CGContextRef)ctx { + UIGraphicsPushContext(ctx); + + CGRect rect = self.bounds; + + CGPoint center = CGPointMake(rect.origin.x + (CGFloat)floor(rect.size.height/2.0f), rect.origin.y + (CGFloat)floor(rect.size.height/2.0f)); + CGFloat lineWidth = self.ringWidth; + CGFloat radius = (CGFloat)floor(MIN(rect.size.width, rect.size.height)/2.0f) - lineWidth; + + //Background + [self.ringBackgroundColor setStroke]; + + UIBezierPath *borderPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0.0f endAngle:2.0f*(CGFloat)M_PI clockwise:NO]; + + [borderPath setLineWidth:lineWidth]; + [borderPath stroke]; + + //Progress + [self.ringColor setStroke]; + + if (self.progress > 0.0f) { + UIBezierPath *processPath = [UIBezierPath bezierPath]; + + [processPath setLineWidth:lineWidth]; + [borderPath setLineCapStyle:(self.roundProgressLine ? kCGLineCapRound : kCGLineCapSquare)]; + + CGFloat startAngle = -((CGFloat)M_PI / 2.0f); + CGFloat endAngle = startAngle + 2.0f * (CGFloat)M_PI * self.progress; + + [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; + + [processPath stroke]; + } +} + +@end + + +@implementation JGProgressHUDRingIndicatorView + +#pragma mark - Initializers + +- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style { + self = [super init]; + + if (self) { + self.layer.contentsScale = [UIScreen mainScreen].scale; + [self.layer setNeedsDisplay]; + + if (style == JGProgressHUDStyleDark) { + self.ringColor = [UIColor whiteColor]; + self.ringBackgroundColor = [UIColor blackColor]; + } + else { + self.ringColor = [UIColor blackColor]; + if (style == JGProgressHUDStyleLight) { + self.ringBackgroundColor = [UIColor colorWithWhite:0.85f alpha:1.0f]; + } + else { + self.ringBackgroundColor = [UIColor colorWithWhite:0.9f alpha:1.0f]; + } + } + } + + return self; +} + +- (instancetype)initWithContentView:(UIView *)contentView { + self = [super initWithContentView:contentView]; + + if (self) { + self.layer.contentsScale = [UIScreen mainScreen].scale; + [self.layer setNeedsDisplay]; + + self.ringColor = [UIColor whiteColor]; + self.ringBackgroundColor = [UIColor blackColor]; + self.ringWidth = 3.0f; + } + + return self; +} + +#pragma mark - Getters & Setters + +- (void)setRoundProgressLine:(BOOL)roundProgressLine { + if (roundProgressLine == self.roundProgressLine) { + return; + } + + _roundProgressLine = roundProgressLine; + + [(JGProgressHUDRingIndicatorLayer *)self.layer setRoundProgressLine:self.roundProgressLine]; +} + +- (void)setRingColor:(UIColor *)tintColor { + if ([tintColor isEqual:self.ringColor]) { + return; + } + + _ringColor = tintColor; + + [(JGProgressHUDRingIndicatorLayer *)self.layer setRingColor:self.ringColor]; +} + +- (void)setRingBackgroundColor:(UIColor *)backgroundTintColor { + if ([backgroundTintColor isEqual:self.ringBackgroundColor]) { + return; + } + + _ringBackgroundColor = backgroundTintColor; + + [(JGProgressHUDRingIndicatorLayer *)self.layer setRingBackgroundColor:self.ringBackgroundColor]; +} + +- (void)setRingWidth:(CGFloat)ringWidth { + if (fequal(ringWidth, self.ringWidth)) { + return; + } + + _ringWidth = ringWidth; + + [(JGProgressHUDRingIndicatorLayer *)self.layer setRingWidth:self.ringWidth]; +} + +- (void)setProgress:(float)progress animated:(BOOL)animated { + if (fequal(self.progress, progress)) { + return; + } + + [super setProgress:progress animated:animated]; + + [CATransaction begin]; + [CATransaction setAnimationDuration:(animated ? 0.3 : 0.0)]; + [(JGProgressHUDRingIndicatorLayer *)self.layer setProgress:self.progress]; + [CATransaction commit]; +} + +#pragma mark - Overrides + ++ (Class)layerClass { + return [JGProgressHUDRingIndicatorLayer class]; +} + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.h b/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.h new file mode 100755 index 0000000..d76a5e7 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.h @@ -0,0 +1,18 @@ +// +// JGProgressHUDSuccessIndicatorView.h +// JGProgressHUD +// +// Created by Jonas Gessner on 19.08.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDIndicatorView.h" + +@interface JGProgressHUDSuccessIndicatorView : JGProgressHUDIndicatorView + +/** + Default initializer for this class. + */ +- (instancetype)init; + +@end diff --git a/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.m b/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.m new file mode 100755 index 0000000..dafaed1 --- /dev/null +++ b/Anyway/JGProgressHUD/JGProgressHUDSuccessIndicatorView.m @@ -0,0 +1,27 @@ +// +// JGProgressHUDSuccessIndicatorView.m +// JGProgressHUD +// +// Created by Jonas Gessner on 19.08.14. +// Copyright (c) 2014 Jonas Gessner. All rights reserved. +// + +#import "JGProgressHUDSuccessIndicatorView.h" + +@implementation JGProgressHUDSuccessIndicatorView + +- (instancetype)initWithContentView:(UIView * __unused)contentView { + NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"JGProgressHUD Resources" ofType:@"bundle"]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[bundlePath stringByAppendingPathComponent:@"jg_hud_success.png"]]]; + + self = [super initWithContentView:imageView]; + + return self; +} + +- (instancetype)init { + return [self initWithContentView:nil]; +} + +@end diff --git a/Anyway/Localization.swift b/Anyway/Localization.swift new file mode 100644 index 0000000..1de7f7c --- /dev/null +++ b/Anyway/Localization.swift @@ -0,0 +1,273 @@ +// +// Localization.swift +// Anyway +// +// Created by Aviel Gross on 16/11/2015. +// Copyright © 2015 Hasadna. All rights reserved. +// + +import Foundation + + +var localization = [ + "SUG_DEREH": [ + "1": "עירוני בצומת", + "2": "עירוני לא בצומת", + "3": "לא עירוני בצומת", + "4": "לא עירוני לא בצומת" + ], + "YEHIDA": [ + "11" : "מרחב חוף (חיפה)", + "12" : "מרחב גליל", + "14" : "מרחב עמקים", + "20" : "מרחב ת\"א", + "33" : "מרחב אילת", + "34" : "מרחב הנגב", + "36" : "מרחב שמשון (עד 1999)", + "37" : "מרחב שמשון (החל ב-2004)", + "38" : "מרחב לכיש", + "41" : "מרחב שומרון", + "43" : "מרחב יהודה", + "51" : "מרחב השרון", + "52" : "מרחב השפלה", + "61" : "מחוז ירושלים" + ], + "SUG_YOM": [ + "1" : "חג", + "2" : "ערב חג", + "3" : "חול המועד", + "4" : "יום אחר", + ], + "HUMRAT_TEUNA": [ + "1" : "קטלנית", + "2" : "קשה", + "3" : "קלה", + ], + "SUG_TEUNA": [ + "1" : "פגיעה בהולך רגל", + "2" : "התנגשות חזית אל צד", + "3" : "התנגשות חזית באחור", + "4" : "התנגשות צד בצד", + "5" : "התנגשות חזית אל חזית", + "6" : "התנגשות עם רכב שנעצר ללא חניה", + "7" : "התנגשות עם רכב חונה", + "8" : "התנגשות עם עצם דומם", + "9" : "ירידה מהכביש או עלייה למדרכה", + "10" : "התהפכות", + "11" : "החלקה", + "12" : "פגיעה בנוסע בתוך כלי רכב", + "13" : "נפילה ברכב נע", + "14" : "שריפה", + "15" : "אחר", + "17" : "התנגשות אחור אל חזית", + "18" : "התנגשות אחור אל צד", + "19" : "התנגשות עם בעל חיים", + "20" : "פגיעה ממטען של רכב", + "21" : "איחוד הצלה", + ], + "ZURAT_DEREH": [ + "1" : "כניסה למחלף", + "2" : "ביציאה ממחלף", + "3" : "מ.חניה/ת. דלק", + "4" : "שיפוע תלול", + "5" : "עקום חד", + "6" : "על גשר מנהרה", + "7" : "מפגש מסילת ברזל", + "8" : "כביש ישר/צומת", + "9" : "אחר", + ], + "HAD_MASLUL": [ + "1" : "חד סיטרי", + "2" : "דו סיטרי+קו הפרדה רצוף", + "3" : "דו סיטרי אין קו הפרדה רצוף", + "4" : "אחר", + ], + "RAV_MASLUL": [ + "1" : "מיפרדה מסומנת בצבע", + "2" : "מיפרדה עם גדר בטיחות", + "3" : "מיפרדה בנויה ללא גדר בטיחות", + "4" : "מיפרדה לא בנויה", + "5" : "אחר", + ], + "MEHIRUT_MUTERET": [ + "1" : "עד 50 קמ\"ש", + "2" : "60 קמ\"ש", + "3" : "70 קמ\"ש", + "4" : "80 קמ\"ש", + "5" : "90 קמ\"ש", + "6" : "100 קמ\"ש", + ], + "TKINUT": [ + "1" : "אין ליקוי", + "2" : "שוליים גרועים", + "3" : "כביש משובש", + "4" : "שוליים גרועים וכביש משובש", + ], + "ROHAV": [ + "1" : "עד 5 מטר", + "2" : "5 עד 7", + "3" : "7 עד 10.5", + "4" : "10.5 עד 14", + "5" : "יותר מ-14", + ], + "SIMUN_TIMRUR": [ + "1" : "סימון לקוי/חסר", + "2" : "תימרור לקוי/חסר", + "3" : "אין ליקוי", + "4" : "לא נדרש תמרור", + ], + "TEURA": [ + "1" : "אור יום רגיל", + "2" : "ראות מוגבלת עקב מזג אויר (עשן,ערפל)", + "3" : "לילה פעלה תאורה", + "4" : "קיימת תאורה בלתי תקינה/לא פועלת", + "5" : "לילה לא קיימת תאורה", + ], + "BAKARA": [ + "1" : "אין בקרה", + "2" : "רמזור תקין", + "3" : "רמזור מהבהב צהוב", + "4" : "רמזור לא תקין", + "5" : "תמרור עצור", + "6" : "תמרור זכות קדימה", + "7" : "אחר", + ], + "MEZEG_AVIR": [ + "1" : "בהיר", + "2" : "גשום", + "3" : "שרבי", + "4" : "ערפילי", + "5" : "אחר", + ], + "PNE_KVISH": [ + "1" : "יבש", + "2" : "רטוב ממים", + "3" : "מרוח בחומר דלק", + "4" : "מכוסה בבוץ", + "5" : "חול או חצץ על הכביש", + "6" : "אחר", + ], + "SUG_EZEM": [ + "1" : "עץ", + "2" : "עמוד חשמל/תאורה/טלפון", + "3" : "תמרור ושלט", + "4" : "גשר סימניו ומגיניו", + "5" : "מבנה", + "6" : "גדר בטיחות לרכב", + "7" : "חבית", + "8" : "אחר", + ], + "MERHAK_EZEM": [ + "1" : "עד מטר", + "2" : "1-3 מטר", + "3" : "על הכביש", + "4" : "על שטח הפרדה", + ], + "LO_HAZA": [ + "1" : "הלך בכיוון התנועה", + "2" : "הלך נגד", + "3" : "שיחק על הכביש", + "4" : "עמד על הכביש", + "5" : "היה על אי הפרדה", + "6" : "היה על שוליים/מדרכה", + "7" : "אחר", + ], + "OFEN_HAZIYA": [ + "1" : "התפרץ אל הכביש", + "2" : "חצה שהוא מוסתר", + "3" : "חצה רגיל", + "4" : "אחר", + ], + "MEKOM_HAZIYA": [ + "1" : "לא במעבר חציה ליד צומת", + "2" : "לא במעבר חציה לא ליד צומת", + "3" : "במעבר חציה בלי רמזור", + "4" : "במעבר חציה עם רמזור", + ], + "KIVUN_HAZIYA": [ + "1" : "מימין לשמאל", + "2" : "משמאל לימין", + ], + "STATUS_IGUN": [ + "1" : "עיגון מדויק", + "2" : "מרכז ישוב", + "3" : "מרכז דרך", + "4" : "מרכז קילומטר", + "9" : "לא עוגן", + ] +] + +var fields = [ + "pk_teuna_fikt": "מזהה", + "SUG_DEREH": "סוג דרך", + "SHEM_ZOMET": "שם צומת", + "SEMEL_YISHUV": "ישוב", + "REHOV1": "רחוב 1", + "REHOV2": "רחוב 2", + "BAYIT": "מספר בית", + "ZOMET_IRONI": "צומת עירוני", + "KVISH1": "כביש 1", + "KVISH2": "כביש 2", + "ZOMET_LO_IRONI": "צומת לא עירוני", + "YEHIDA": "יחידה", + "SUG_YOM": "סוג יום", + "RAMZOR": "רמזור", + "HUMRAT_TEUNA": "חומרת תאונה", + "SUG_TEUNA": "סוג תאונה", + "ZURAT_DEREH": "צורת דרך", + "HAD_MASLUL": "חד מסלול", + "RAV_MASLUL": "רב מסלול", + "MEHIRUT_MUTERET": "מהירות מותרת", + "TKINUT": "תקינות", + "ROHAV": "רוחב", + "SIMUN_TIMRUR": "סימון תמרור", + "TEURA": "תאורה", + "BAKARA": "בקרה", + "MEZEG_AVIR": "מזג אוויר", + "PNE_KVISH": "פני כביש", + "SUG_EZEM": "סוג עצם", + "MERHAK_EZEM": "מרחק עצם", + "LO_HAZA": "לא חצה", + "OFEN_HAZIYA": "אופן חציה", + "MEKOM_HAZIYA": "מקום חציה", + "KIVUN_HAZIYA": "כיוון חציה", + "STATUS_IGUN": "עיגון", + "MAHOZ": "מחוז", + "NAFA": "נפה", + "EZOR_TIVI": "אזור טבעי", + "MAAMAD_MINIZIPALI": "מעמד מוניציפלי", + "ZURAT_ISHUV": "צורת יישוב", + + "SUG_MEORAV": "סוג מעורב", + "SHNAT_HOZAA": "שנת הוצאת רשיון נהיגה", + "KVUZA_GIL": "קבוצת גיל", + "MIN": "מין", + "SUG_REHEV_NASA_LMS": "סוג רכב בו נסע", + "EMZAE_BETIHUT": "אמצעי בטיחות", + "HUMRAT_PGIA": "חומרת פגיעה", + "SUG_NIFGA_LMS": "סוג נפגע", + "PEULAT_NIFGA_LMS": "מיקום פצוע", + "KVUTZAT_OHLUSIYA_LMS": "קבוצת אוכלוסיה", + "MAHOZ_MEGURIM": "מחוז מגורים", + "NAFA_MEGURIM": "נפת מגורים", + "EZOR_TIVI_MEGURIM": "אזור טבעי מגורים", + "MAAMAD_MINIZIPALI_MEGURIM": "מעמד מוניצפלי מגורים", + "ZURAT_ISHUV_MEGURIM": "צורת ישוב מגורים", + + "PAZUA_USHPAZ": "משך אשפוז", + "MADAD_RAFUI": "מדד רפואי לחומרת הפציעה - ISS", + "YAAD_SHIHRUR": "יעד שחרור", + "SHIMUSH_BE_AVIZAREY_BETIHOT": "שימוש באביזרי בטיחות", + "PTIRA_MEUHERET": "מועד הפטירה", + + "NEFAH": "נפח מנוע", + "SHNAT_YITZUR": "שנת ייצור", + "KIVUNE_NESIA": "כיוון נסיעה", + "MATZAV_REHEV": "מצב רכב", + "SHIYUH_REHEV_LMS": "שיוך רכב", + "SUG_REHEV_LMS": "סוג רכב", + "MEKOMOT_YESHIVA_LMS": "מקומות ישיבה", + "MISHKAL_KOLEL_LMS": "משקל כולל", + "ACC_ID": "מספר סידורי", + "PROVIDER_CODE": "סוג תיק" +] \ No newline at end of file diff --git a/Anyway/MapProvider.swift b/Anyway/MapProvider.swift new file mode 100644 index 0000000..bc3bb4a --- /dev/null +++ b/Anyway/MapProvider.swift @@ -0,0 +1,224 @@ +// +// MapProvider.swift +// Anyway +// +// Created by Aviel Gross on 2/16/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import Foundation +import MapKit +import Alamofire +import SwiftyJSON + +let MAX_DIST_OF_MAP_EDGES = 10000 +let MIN_DIST_CLUSTER_DISABLE = 1000 + +typealias Edges = (ne: Coordinate, sw: Coordinate) + +extension MKMapView { + func edgePoints() -> Edges { + let nePoint = CGPoint(x: self.bounds.maxX, y: self.bounds.origin.y) + let swPoint = CGPoint(x: self.bounds.minX, y: self.bounds.maxY) + let neCoord = self.convertPoint(nePoint, toCoordinateFromView: self) + let swCoord = self.convertPoint(swPoint, toCoordinateFromView: self) + return (ne: neCoord, sw: swCoord) + } + + func edgesDistance() -> CLLocationDistance { + let edges = self.edgePoints() + return CLLocation.distance(from: edges.sw, to: edges.ne) + } +} + +extension CLLocation { + // In meteres + class func distance(from from: CLLocationCoordinate2D, to:CLLocationCoordinate2D) -> CLLocationDistance { + let from = CLLocation(latitude: from.latitude, longitude: from.longitude) + let to = CLLocation(latitude: to.latitude, longitude: to.longitude) + return from.distanceFromLocation(to) + } +} + +class Network { + + var currentRequest: Request? = nil + + func cancelRequestIfNeeded() { + if let current = currentRequest { current.cancel() } + } + + func getAnnotations(edges: Edges, filter: Filter, anots: (markers: [MarkerAnnotation], totalCount: Int)->()) { + + let ne_lat = edges.ne.latitude // 32.158091269627874 + let ne_lng = edges.ne.longitude // 34.88087036877948 + let sw_lat = edges.sw.latitude // 32.146882347101766 + let sw_lng = edges.sw.longitude // 34.858318355382266 + let zoom = 16 + let thinMarkers = true + let startDate = Int(filter.startDate.timeIntervalSince1970) + let endDate = Int(filter.endDate.timeIntervalSince1970) + let showFatal = filter.showFatal ? 1 : 0 + let showSevere = filter.showSevere ? 1 : 0 + let showLight = filter.showLight ? 1 : 0 + let showInaccurate = filter.showInaccurate ? 1 : 0 + + print("Fetching with filter:\n\(filter.description)") + + let params: [String : AnyObject] = [ + "ne_lat" : ne_lat, + "ne_lng" : ne_lng, + "sw_lat" : sw_lat, + "sw_lng" : sw_lng, + "zoom" : zoom, + "thin_markers" : thinMarkers, + "start_date" : startDate, + "end_date" : endDate, + "show_fatal" : showFatal, + "show_severe" : showSevere, + "show_light" : showLight, + "show_inaccurate" : showInaccurate, + "show_markers" : 1, + "show_discussions" : 1 + ] + + print("params: \(params)") + + cancelRequestIfNeeded() + + UIApplication.sharedApplication().networkActivityIndicatorVisible = true + + let response = { (req: NSURLRequest, response: NSHTTPURLResponse?, json: JSON, err: NSError?) -> Void in + UIApplication.sharedApplication().networkActivityIndicatorVisible = false + print("response from server ended") + + if err == nil { + let markers = self.parseJson(json) + + //Sometimes multiple markers would have the exact same coordinate. + //This method would arrange the identical markers in a circle around the coordinate. + //AnnotationCoordinateUtility.mutateCoordinatesOfClashingAnnotations(markers) + print("markers:\(markers.count)") + let finalMarkers = self.groupMarkersWithColidingCoordinates(markers) + + anots(markers: finalMarkers, totalCount: markers.count) + } else { + print("Error! \(err)") + anots(markers: [], totalCount: 0) + } + } + + + currentRequest = Alamofire.request(.GET, "http://www.anyway.co.il/markers", parameters: params, encoding: .URL, headers: nil) + + /* Raw response, for debug */ +// .response({ (request, response, data, error) -> Void in +// if let string = data as? String { +// println("response: ###\(string)###") +// } else if let errorObj = error { +// println("error: \(errorObj)") +// } else { +// println("no response AND no error") +// } +// }) + + /* JSON+Alamofire */ + .responseSwiftyJSON(response) + + } + + /* + Checking for coliding Marker group and creating MarkerGroup for them + */ + private func groupMarkersWithColidingCoordinates(markers: [Marker]) -> [MarkerAnnotation] { + + var markerAnnotations = [MarkerAnnotation]() + + let annotsDict = AnnotationCoordinateUtility.groupAnnotationsByLocationValue(markers) as! [NSValue:[Marker]] + for (coordVal, annotsAtLocation) in annotsDict { + if annotsAtLocation.count > 1 { + let group = MarkerGroup(markers: annotsAtLocation)! + //print("Added markerGroup of \(group.markers.count) markers at \(coordVal)") + markerAnnotations.append(group) + } else { + markerAnnotations.append(annotsAtLocation.first!) + } + } + + return markerAnnotations + } + + /* + Parsing server JSON response to [Marker], ignoring coliding markers + */ + private func parseJson(json: JSON) -> [Marker] { + + var annots = [Marker]() + + if let markers = json["markers"].array { + + for marker in markers { + + let lat = marker["latitude"].number!.doubleValue + let lng = marker["longitude"].number!.doubleValue + let coord = CLLocationCoordinate2DMake(lat, lng) + + let address = marker["address"].string ?? "" + let content = marker["description"].string ?? "" + let title = marker["title"].string ?? "" + + let created: NSDate = { + if let createdRaw = marker["created"].string { + let form = NSDateFormatter() + form.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + return form.dateFromString(createdRaw) ?? NSDate(timeIntervalSince1970: 0) + } + return NSDate(timeIntervalSince1970: 0) + }() + + let id = Int(marker["id"].string ?? "") ?? 0 + let accuracy = marker["locationAccuracy"].number ?? 0 + let severity = marker["severity"].number ?? 0 + let subtype = marker["subtype"].number ?? 0 + let type = marker["type"].number ?? 0 + + let mView = Marker(coord: coord, address: address, content: content, title: title, created: created, id: id, accuracy: accuracy.integerValue, severity: severity.integerValue, subtype: subtype.integerValue, type: type.integerValue) + + mView.roadShape = marker["roadShape"].intValue + mView.cross_mode = marker["cross_mode"].intValue + mView.secondaryStreet = marker["secondaryStreet"].stringValue + mView.cross_location = marker["cross_location"].intValue + mView.one_lane = marker["one_lane"].intValue + mView.speed_limit = marker["speed_limit"].intValue + mView.weather = marker["weather"].intValue + mView.provider_code = marker["secondaryStreet"].stringValue + mView.road_object = marker["road_object"].intValue + mView.didnt_cross = marker["didnt_cross"].intValue + mView.object_distance = marker["object_distance"].intValue + mView.road_sign = marker["road_sign"].intValue + mView.intactness = marker["intactness"].intValue + mView.junction = marker["secondaryStreet"].stringValue + mView.road_control = marker["road_control"].intValue + mView.road_light = marker["road_light"].intValue + mView.multi_lane = marker["multi_lane"].intValue + mView.dayType = marker["dayType"].intValue + mView.unit = marker["unit"].intValue + mView.road_width = marker["road_width"].intValue + mView.cross_direction = marker["cross_direction"].intValue + mView.roadType = marker["roadType"].intValue + mView.road_surface = marker["road_surface"].intValue + mView.mainStreet = marker["secondaryStreet"].stringValue + + + annots.append(mView) + } + + } + + return annots + } +} + + + + diff --git a/Anyway/Marker.swift b/Anyway/Marker.swift new file mode 100644 index 0000000..5162b16 --- /dev/null +++ b/Anyway/Marker.swift @@ -0,0 +1,218 @@ +// +// Marker.swift +// Anyway +// +// Created by Aviel Gross on 2/16/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit +import MapKit + +typealias Coordinate = CLLocationCoordinate2D + +//MARK: - Protocols + +@objc protocol MarkerAnnotation: class, NSObjectProtocol, MKAnnotation {} + +protocol VisualMarker: MarkerAnnotation { + var iconName: String? { get } +} + + +//MARK: - MarkerGroup + +class MarkerGroup : NSObject, MarkerAnnotation { + + var title: String? { return "\(markers.count)" } + var coordinate: Coordinate + + var markers: [Marker] = [] + var highestSeverity: Int = 0 + + convenience init?(markers: [Marker]) { + self.init(coordinate: CLLocationCoordinate2DMake(0, 0)) + + if let coord = markers.first?.coordinate { + self.coordinate = coord + } + self.markers = markers + for m in markers { + highestSeverity = max(highestSeverity, m.severity) + } + if markers.count < 1 { return nil } + } + + init(coordinate: CLLocationCoordinate2D) { + self.coordinate = coordinate + } + +} + +/// Implement "subtitle" param declared in 'MKAnnotation' +extension MarkerGroup { + var subtitle: String? { return markers.count == 1 ? "תאונה אחת" : "\(markers.count) תאונות" } +} + +extension MarkerGroup : VisualMarker { + var iconName: String? { + switch highestSeverity { + case Severity.Severe.rawValue: return "multiple_severe" + case Severity.Various.rawValue: return "multiple_various" + case Severity.Fatal.rawValue: return "multiple_lethal" + case Severity.Light.rawValue: return "multiple_medium" + default: return "multiple_various" + } + } +} + + + +//MARK: - Marker + +class Marker : NSObject, MarkerAnnotation { + + var title: String? { return localizedSubtype } + var coordinate: CLLocationCoordinate2D + + var address: String = "" + var descriptionContent: String = "" + var titleAccident: String = "" + var created: NSDate = NSDate(timeIntervalSince1970: 0) + var followers: [AnyObject] = [] + var following: Bool = false + var id: Int = 0 + var locationAccuracy: Int = 0 + var severity: Int = 0 + var subtype: Int = 0 + var type: Int = 0 + var user: String = "" + + //new + var roadShape: Int = -1 + var cross_mode: Int = -1 + var secondaryStreet: String = "" + var cross_location: Int = -1 + var one_lane: Int = -1 + var speed_limit: Int = -1 + var weather: Int = -1 + var provider_code: String = "" + var road_object: Int = -1 + var didnt_cross: Int = -1 + var object_distance: Int = -1 + var road_sign: Int = -1 + var intactness: Int = -1 + var junction: String = "" + var road_control: Int = -1 + var road_light: Int = -1 + var multi_lane: Int = -1 + var dayType: Int = -1 + var unit: Int = -1 + var road_width: Int = -1 + var cross_direction: Int = -1 + var roadType: Int = -1 + var road_surface: Int = -1 + var mainStreet: String = "" + + convenience init(coord: Coordinate, address: String, content: String, title: String, created: NSDate, id: Int, accuracy: Int, severity: Int, subtype: Int, type: Int) { + self.init(coordinate: coord) + self.coordinate = coord + self.address = address + self.descriptionContent = content + self.titleAccident = title + self.created = created + self.id = id + self.locationAccuracy = accuracy + self.severity = severity + self.subtype = subtype + self.type = type + } + + init(coordinate: CLLocationCoordinate2D) { + self.coordinate = coordinate + } +} + +/// Implement "subtitle" param declared in 'MKAnnotation' +extension Marker { + var subtitle: String? { return localizedSeverity } +} + +/// Localized descriptions for Marker +extension Marker: VisualMarker { + //MARK: Localized Info + + var localizedSubtype: String { + switch self.subtype { + case 1: return "פגיעה בהולך רגל" + case 2: return "התנגשות חזית אל צד" + case 3: return "התנגשות חזית באחור" + case 4: return "התנגשות צד בצד" + case 5: return "התנגשות חזית אל חזית" + case 6: return "התנגשות עם רכב חונה" + case 7: return "התנגשות עם עצם דומם" + case 8: return "ירידה מהכביש או עלייה למדרכה" + case 9: return "ירידה מהכביש או עלייה למדרכה" + case 10: return "התהפכות" + case 11: return "החלקה" + case 12: return "פגיעה בנוסע בתוך כלי רכב" + case 13: return "נפילה ברכב נע" + case 14: return "שריפה" + case 15: return "אחר" + case 17: return "התנגשות אחור אל חזית" + case 18: return "התנגשות אחור אל צד" + case 19: return "התנגשות עם בעל חיים" + case 20: return "פגיעה ממטען של רכב" + default: return "" + } + } + + var localizedSeverity: String { + switch self.severity { + case 1: return "קטלנית" + case 2: return "קשה" + case 3: return "קלה" + default: return "" + } + } + + var localizedAccuracy: String { + switch self.locationAccuracy { + case 1: return "עיגון מדויק" + case 2: return "מרכז ישוב" + case 3: return "מרכז דרך" + case 4: return "מרכז קילומטר" + case 9: return "לא עוגן" + default: return "" + } + } + + + var iconName: String? { + var icons = [Severity:[AccidentType:String]]() + icons[Severity.Fatal] = [ + AccidentType.CarToPedestrian : "vehicle_person_lethal.png", + AccidentType.CarToCar : "vehicle_vehicle_lethal.png", + AccidentType.CarToObject : "vehicle_object_lethal.png"] + icons[Severity.Severe] = [ + AccidentType.CarToPedestrian : "vehicle_person_severe.png", + AccidentType.CarToCar : "vehicle_vehicle_severe.png", + AccidentType.CarToObject : "vehicle_object_severe.png"] + icons[Severity.Light] = [ + AccidentType.CarToPedestrian : "vehicle_person_medium.png", + AccidentType.CarToCar : "vehicle_vehicle_medium.png", + AccidentType.CarToObject : "vehicle_object_medium.png"] + + if let sev = Severity(rawValue: severity), + let someIcons = icons[sev], + let type = accidentMinorTypeToType(subtype), + let icon = someIcons[type] { + return icon + } + + return nil + } +} + + + diff --git a/Anyway/MarkerViews.swift b/Anyway/MarkerViews.swift new file mode 100644 index 0000000..8e6a1ce --- /dev/null +++ b/Anyway/MarkerViews.swift @@ -0,0 +1,100 @@ +// +// MarkerViews.swift +// Anyway +// +// Created by Aviel Gross on 9/1/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import Foundation + +let markerReuseIdentifierDefault = "MarkerIdentifier" +let markerGroupReuseIdentifierDefault = "MarkerGroupIdentifier" +let clusterReuseIdentifierDefault = "ClusterIdentifier" + +class MarkerView: MKPinAnnotationView { + + convenience init(marker: Marker, reuseIdentifier: String! = markerReuseIdentifierDefault) { + self.init(annotation: marker, reuseIdentifier: reuseIdentifier) + enabled = true + canShowCallout = true + if let name = marker.iconName { + image = UIImage(named: name) //TODO fallback? + } + rightCalloutAccessoryView = UIButton(type: UIButtonType.DetailDisclosure) as UIView + // leftCalloutAccessoryView + } + +} + +class MarkerGroupView: MKPinAnnotationView { + + convenience init(markerGroup: MarkerGroup, reuseIdentifier: String! = markerGroupReuseIdentifierDefault) { + self.init(annotation: markerGroup, reuseIdentifier: reuseIdentifier) + enabled = true + canShowCallout = true + + // self.pinColor = MKPinAnnotationColor.Green + if let name = markerGroup.iconName { + image = UIImage(named: name) //TODO fallback? + } + //rightCalloutAccessoryView + //leftCalloutAccessoryView + } + +} + +class ClusterView: MKAnnotationView { + + var label: UILabel? + var backImage: UIImageView? + + override init(annotation: MKAnnotation?, reuseIdentifier: String?) { + super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) + setup() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + func setup() { + let aLabel = UILabel(frame: CGRect(origin: CGPointZero, size: CGSizeMake(30, 30))) + aLabel.textAlignment = .Center + aLabel.font = UIFont.systemFontOfSize(14) + // aLabel.backgroundColor = UIColor.whiteColor() + // aLabel.layer.borderColor = UIColor.grayColor().CGColor + // aLabel.layer.borderWidth = 0.5 + // aLabel.layer.cornerRadius = aLabel.frame.width / 2 + // aLabel.layer.masksToBounds = true + + + let anImage = UIImageView(image: UIImage(named: "cluster_1")!) + if let cluster = annotation as? OCAnnotation { + anImage.image = clusterImageForClusterCount(cluster.annotationsInCluster().count) + } + anImage.frame.size = CGSizeMake(30, 30) + + + label = aLabel + backImage = anImage + addSubview(backImage!) + addSubview(label!) + } + + func clusterImageForClusterCount(count: Int) -> UIImage { + switch count { + case let x where x < 10: return UIImage(named: "cluster_1")! + case let x where x < 50: return UIImage(named: "cluster_2")! + case let x where x < 100: return UIImage(named: "cluster_3")! + default: return UIImage(named: "cluster_4")! + } + } + +} \ No newline at end of file diff --git a/Anyway/OCMapView/OCAlgorithmDelegate.h b/Anyway/OCMapView/OCAlgorithmDelegate.h new file mode 100755 index 0000000..8430d61 --- /dev/null +++ b/Anyway/OCMapView/OCAlgorithmDelegate.h @@ -0,0 +1,24 @@ +// +// OCAlgorithmDelegate.h +// OClusterMapView+Sample +// +// Created by Markus on 24.09.13. +// +// + +#import + +/// Protocol for notifying on Cluster events. NOT in use yet. +/** Implement this protocol if you are using asynchronous clustering algorithms. + In fact, there isn't one yet. This just demonstrates where this class will develop to in future.*/ +@protocol OCAlgorithmDelegate +@required +/// Called when an algorithm finishes a block of calculations +- (NSArray *)algorithmClusteredPartially; +@optional +/// Called when algorithm starts calculating. +- (void)algorithmDidBeganClustering; +/// Called when algorithm finishes calculating. +- (void)algorithmDidFinishClustering; +@end + diff --git a/Anyway/OCMapView/OCAlgorithms.h b/Anyway/OCMapView/OCAlgorithms.h new file mode 100755 index 0000000..b67be66 --- /dev/null +++ b/Anyway/OCMapView/OCAlgorithms.h @@ -0,0 +1,56 @@ +// +// OCAlgorythms.h +// openClusterMapView +// +// Created by Botond Kis on 15.07.11. +// + +#import +#import + +/// Enumaration for the clustering methods +/** Contains all clustering methods which are aviable in OCMapView yet*/ +typedef enum { + OCClusteringMethodBubble, + OCClusteringMethodGrid +} OCClusteringMethod; + +/// Class containing clustering algorithms. +/** The first release of OCMapView brings two different algorithms. + This class is supposed to hold those algorithms. + More algorithms are planned for future releases of OCMapView. + + Note for OCMapView developers: + Every algorithm has to be a class method which returns an array of OCAnnotations or a subclass of it. + OR for future releases they can be instance methods if they run asynchronously. The instance holder + needs to implement the delegate protocol and the method needs to call the delegate. + */ +@interface OCAlgorithms : NSObject + +/// Bubble clustering with iteration +/** This algorithm creates clusters based on the distance + between single annotations. + + @param annotationsToCluster contains the Annotations that should be clustered + @param radius represents the cluster size. + + It iterates through all annotations in the array and compare their + distances. If they are near engough, they will be clustered.*/ ++ (NSArray*)bubbleClusteringWithAnnotations:(NSArray*)annotationsToCluster + clusterRadius:(CLLocationDistance)radius + grouped:(BOOL)grouped; + + + +/// Grid clustering with predefined size +/** This algorithm creates clusters based on a defined grid. + + @param annotationsToCluster contains the Annotations that should be clustered + @param tileRect represents the size of a grid tile. + + It iterates through all annotations in the array and puts them into a grid tile based on their location.*/ ++ (NSArray*)gridClusteringWithAnnotations:(NSArray*)annotationsToCluster + clusterRect:(MKCoordinateSpan)tileRect + grouped:(BOOL)grouped; + +@end diff --git a/Anyway/OCMapView/OCAlgorithms.m b/Anyway/OCMapView/OCAlgorithms.m new file mode 100755 index 0000000..378aed9 --- /dev/null +++ b/Anyway/OCMapView/OCAlgorithms.m @@ -0,0 +1,136 @@ +// +// OCAlgorythms.m +// openClusterMapView +// +// Created by Botond Kis on 15.07.11. +// + +#import "OCAlgorithms.h" +#import "OCAnnotation.h" +#import "OCDistance.h" +#import "OCGrouping.h" + +static double euclidDistanceSquared(CLLocationCoordinate2D a, CLLocationCoordinate2D b) +{ + double latDelta = a.latitude-b.latitude; + double lonDelta = a.longitude-b.longitude; + return latDelta*latDelta + lonDelta*lonDelta; +} + +@implementation OCAlgorithms + +// Bubble clustering with iteration ++ (NSArray*)bubbleClusteringWithAnnotations:(NSArray*)annotationsToCluster + clusterRadius:(CLLocationDistance)radius + grouped:(BOOL)grouped; +{ + NSMutableArray *clusteredAnnotations = [[NSMutableArray alloc] init]; + + double radiusSquared = radius*radius; + + // Clustering + for (id annotation in annotationsToCluster) + { + // Find fitting existing cluster + BOOL foundCluster = NO; + CLLocationCoordinate2D annotationCoordinate = [annotation coordinate]; + for (OCAnnotation *clusterAnnotation in clusteredAnnotations) { + // If the annotation is in range of the cluster, add it + if(euclidDistanceSquared(annotationCoordinate, [clusterAnnotation coordinate]) <= radiusSquared) { + // check group + if (grouped && [annotation conformsToProtocol:@protocol(OCGrouping)]) { + if (![clusterAnnotation.groupTag isEqualToString:((id )annotation).groupTag]) + continue; + } + + foundCluster = YES; + [clusterAnnotation addAnnotation:annotation]; + break; + } + } + + // If the annotation wasn't added to a cluster, create a new one + if (!foundCluster){ + OCAnnotation *newCluster = [[OCAnnotation alloc] initWithAnnotation:annotation]; + [clusteredAnnotations addObject:newCluster]; + + // check group + if (grouped && [annotation conformsToProtocol:@protocol(OCGrouping)]) { + newCluster.groupTag = [(id)annotation groupTag]; + } + } + } + + // whipe all empty or single annotations + NSMutableArray *returnArray = [[NSMutableArray alloc] init]; + for (OCAnnotation *anAnnotation in clusteredAnnotations) { + if ([anAnnotation.annotationsInCluster count] == 1) { + [returnArray addObject:[anAnnotation.annotationsInCluster lastObject]]; + } else if ([anAnnotation.annotationsInCluster count] > 1){ + [returnArray addObject:anAnnotation]; + } + } + + return returnArray; +} + + +// Grid clustering with predefined size ++ (NSArray*)gridClusteringWithAnnotations:(NSArray*)annotationsToCluster + clusterRect:(MKCoordinateSpan)tileRect + grouped:(BOOL)grouped; +{ + NSMutableDictionary *clusteredAnnotations = [[NSMutableDictionary alloc] init]; + + // iterate through all annotations + for (id annotation in annotationsToCluster) + { + // calculate grid coordinates of the annotation + NSInteger row = ([annotation coordinate].longitude+180.0)/tileRect.longitudeDelta; + NSInteger column = ([annotation coordinate].latitude+90.0)/tileRect.latitudeDelta; + NSString *key = [NSString stringWithFormat:@"%d:%d", (int)row, (int)column]; + + // add group tag to key + if (grouped && [annotation conformsToProtocol:@protocol(OCGrouping)]) { + key = [NSString stringWithFormat: @"%@:%@", key, [(id)annotation groupTag]]; + } + + // get the cluster for the calculated coordinates + OCAnnotation *clusterAnnotation = [clusteredAnnotations objectForKey:key]; + + // if there is none, create one + if (clusterAnnotation == nil) { + clusterAnnotation = [[OCAnnotation alloc] init]; + + CLLocationDegrees lon = row * tileRect.longitudeDelta + tileRect.longitudeDelta/2.0 - 180.0; + CLLocationDegrees lat = (column * tileRect.latitudeDelta) + tileRect.latitudeDelta/2.0 - 90.0; + clusterAnnotation.coordinate = CLLocationCoordinate2DMake(lat, lon); + + // check group + if (grouped && [annotation conformsToProtocol:@protocol(OCGrouping)]) { + clusterAnnotation.groupTag = [(id)annotation groupTag]; + } + + [clusteredAnnotations setValue:clusterAnnotation forKey:key]; + } + + // add annotation to the cluster + [clusterAnnotation addAnnotation:annotation]; + } + + // return array + NSMutableArray *returnArray = [[NSMutableArray alloc] init]; + + // add single annotations directly without OCAnnotation + for (OCAnnotation *anAnnotation in [clusteredAnnotations allValues]) { + if ([anAnnotation.annotationsInCluster count] == 1) { + [returnArray addObject:[anAnnotation.annotationsInCluster lastObject]]; + } else if ([anAnnotation.annotationsInCluster count] > 1) { + [returnArray addObject:anAnnotation]; + } + } + + return returnArray; +} + +@end diff --git a/Anyway/OCMapView/OCAnnotation.h b/Anyway/OCMapView/OCAnnotation.h new file mode 100755 index 0000000..2fa900f --- /dev/null +++ b/Anyway/OCMapView/OCAnnotation.h @@ -0,0 +1,49 @@ +// +// OCAnnotation.h +// openClusterMapView +// +// Created by Botond Kis on 14.07.11. +// + +#import +#import "OCGrouping.h" + +/// Annotation class which represents a Cluster. +/** OCAnnotation stores all annotations which are in its area. + Objects of this class will be handed over to the MKMapView delegate method "viewForAnnotation". + Implements OCGrouping protocol. @see OCGrouping + */ +@interface OCAnnotation : NSObject + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, copy) NSString *groupTag; +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +/// Init with annotations +/** Init cluster with containing annotations*/ +- (id)initWithAnnotation:(id)annotation; + +/// List of annotations in the cluster. +/** Returns all annotations in the cluster. */ +- (NSArray*)annotationsInCluster; + +// Adds an annotation to the cluster +/// Adds a single annotation to the cluster. +/** Adds a given annotation to the cluster and sets the title to the number of containing annotations. */ +- (void)addAnnotation:(id)annotation; + +/// Adds multiple annotations to the cluster. +/** Adds multiple annotations to the cluster and sets the title to the number of containing annotations. + Calls addAnnotation in a loop. */ +- (void)addAnnotations:(NSArray *)annotations; + +/// Removes a single annotation from the cluster. +/** Removes a given annotation from the cluster and sets the title to the number of containing annotations. */ +- (void)removeAnnotation:(id)annotation; + +/// Removes multiple annotations from the cluster. +/** Removes multiple annotations from the cluster and sets the title to the number of containing annotations. */ +- (void)removeAnnotations:(NSArray*)annotations; + +@end diff --git a/Anyway/OCMapView/OCAnnotation.m b/Anyway/OCMapView/OCAnnotation.m new file mode 100755 index 0000000..bbfcbeb --- /dev/null +++ b/Anyway/OCMapView/OCAnnotation.m @@ -0,0 +1,153 @@ +// +// OCAnnotation.m +// openClusterMapView +// +// Created by Botond Kis on 14.07.11. +// + +#import "OCAnnotation.h" + +/// Compares two objects using -isEqual:. For the border case where both objects +/// are nil, it returns YES too (whereas [nil isEqual:nil] == NO and this leads +/// to incorrect comparison behaviour...) +static BOOL eql(id a, id b) +{ + if(a == b) { + return YES; + } else { + return [a isEqual:b]; + } +} + +static CLLocationDegrees const OCMinimumInvalidDegree = 400.0; + +@interface OCAnnotation () + +@property (nonatomic, strong) NSMutableArray *annotationsInCluster; +@property (nonatomic, assign) CLLocationCoordinate2D minCoordinate; +@property (nonatomic, assign) CLLocationCoordinate2D maxCoordinate; + +@end + + + +@implementation OCAnnotation + +- (id)init +{ + self = [super init]; + if (self) { + _annotationsInCluster = [[NSMutableArray alloc] init]; + _minCoordinate.latitude = OCMinimumInvalidDegree; + _maxCoordinate.latitude = OCMinimumInvalidDegree; + } + return self; +} + +- (id)initWithAnnotation:(id)annotation; +{ + self = [self init]; + if (self) { + CLLocationCoordinate2D annotationCoordinate = [annotation coordinate]; + _minCoordinate = annotationCoordinate; + _maxCoordinate = annotationCoordinate; + _coordinate = annotationCoordinate; + [_annotationsInCluster addObject:annotation]; + + if ([annotation respondsToSelector:@selector(title)]) { + self.title = [annotation title]; + } + if ([annotation respondsToSelector:@selector(subtitle)]) { + self.subtitle = [annotation subtitle]; + } + } + + return self; +} + +// +// List of annotations in the cluster +// read only +- (NSArray*)annotationsInCluster; +{ + return [_annotationsInCluster copy]; +} + +#pragma mark add / remove annotations + +- (void)addAnnotation:(id)annotation; +{ + CLLocationCoordinate2D annotationCoordinate = annotation.coordinate; + // check if min and max have been set + if (self.minCoordinate.latitude >= OCMinimumInvalidDegree) { + _minCoordinate = annotationCoordinate; + } + if (self.maxCoordinate.latitude >= OCMinimumInvalidDegree) { + _maxCoordinate = annotationCoordinate; + } + + // Add annotation to the cluster + [_annotationsInCluster addObject:annotation]; + + // recompute center coordinate + _minCoordinate.latitude = MIN(_minCoordinate.latitude, annotationCoordinate.latitude); + _minCoordinate.longitude = MIN(_minCoordinate.longitude,annotationCoordinate.longitude); + _maxCoordinate.latitude = MAX(_maxCoordinate.latitude, annotationCoordinate.latitude); + _maxCoordinate.longitude = MAX(_maxCoordinate.longitude, annotationCoordinate.longitude); + + _coordinate.latitude = _minCoordinate.latitude + (_maxCoordinate.latitude-_minCoordinate.latitude)/2.0; + _coordinate.longitude = _minCoordinate.longitude + (_maxCoordinate.longitude-_minCoordinate.longitude)/2.0; +} + +- (void)addAnnotations:(NSArray *)annotations; +{ + for (id annotation in annotations) { + [self addAnnotation: annotation]; + } +} + +- (void)removeAnnotation:(id)annotation; +{ + // Remove annotation from cluster + [_annotationsInCluster removeObject:annotation]; +} + +- (void)removeAnnotations:(NSArray*)annotations; +{ + for (id annotation in annotations) { + [self removeAnnotation: annotation]; + } +} + +#pragma mark equality + +- (BOOL)isEqual:(OCAnnotation*)annotation; +{ + if (![annotation isKindOfClass:[OCAnnotation class]]) { + return NO; + } + + if(self.coordinate.latitude == annotation.coordinate.latitude && + self.coordinate.longitude == annotation.coordinate.longitude && + eql(self.title, annotation.title) && + eql(self.subtitle, annotation.subtitle) && + eql(self.groupTag, annotation.groupTag)) + { + // I compare in two steps so the set computations don't have to be done so often. + NSSet *a_annotationsInCluster = [[NSSet alloc] initWithArray:self.annotationsInCluster]; + NSSet *b_annotationsInCluster = [[NSSet alloc] initWithArray:annotation.annotationsInCluster]; + if([a_annotationsInCluster isEqual:b_annotationsInCluster]) { + return YES; + } + } + + return NO; +} + +- (NSUInteger)hash +{ + return self.title.hash*97 + self.subtitle.hash*13 + (NSUInteger)(self.coordinate.latitude*999); +} + +@end + diff --git a/Anyway/OCMapView/OCDistance.h b/Anyway/OCMapView/OCDistance.h new file mode 100755 index 0000000..3d3d011 --- /dev/null +++ b/Anyway/OCMapView/OCDistance.h @@ -0,0 +1,20 @@ +// +// OEDistance.h +// openClusterMapView +// +// Created by Botond Kis on 14.02.11. +// + +#import +#import + +/** @fn double CLLocationCoordinateDistance(CLLocationCoordinate2D c1, CLLocationCoordinate2D c2) + @brief calculates the distance between two given coordinates in meters + */ +double CLLocationCoordinateDistance(CLLocationCoordinate2D c1, CLLocationCoordinate2D c2); + +/// Returns YES if pt is contained within the bounds or region +BOOL MKCoordinateRegionContainsPoint(MKCoordinateRegion region, CLLocationCoordinate2D pt); + +/// Returns YES if b is fully contained in a (i.e. the intersection of a and b = b) +BOOL MKCoordinateRegionContainsRegion(MKCoordinateRegion a, MKCoordinateRegion b); diff --git a/Anyway/OCMapView/OCDistance.m b/Anyway/OCMapView/OCDistance.m new file mode 100755 index 0000000..df61ff2 --- /dev/null +++ b/Anyway/OCMapView/OCDistance.m @@ -0,0 +1,80 @@ +// +// OEDistance.h +// openClusterMapView +// +// Created by Botond Kis on 14.02.11. +// + +#import "OCDistance.h" + +#define kDegreesToRadians (M_PI / 180.0) + +/// calculates the distance between two given coordinates in meters +double CLLocationCoordinateDistance(CLLocationCoordinate2D a, CLLocationCoordinate2D b) +{ + double earthRadius = 6371.01; // Earth's radius in Kilometers + + // Get the difference between our two points then convert the difference into radians + double nDLat = (a.latitude - b.latitude) * kDegreesToRadians; + double nDLon = (a.longitude - b.longitude) * kDegreesToRadians; + + double fromLat = b.latitude * kDegreesToRadians; + double toLat = a.latitude * kDegreesToRadians; + + double nA = pow ( sin(nDLat/2), 2 ) + cos(fromLat) * cos(toLat) * pow ( sin(nDLon/2), 2 ); + + double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA )); + double nD = earthRadius * nC; + + return nD * 1000; // Return our calculated distance in meters +} + +/// Calculates x = (pt + k*modul) such that x is in [0, modul) and k is a natural number +static double modulo(double pt, double modul) +{ + while(pt < 0) { + pt += modul; + } + while(pt >= modul) { + pt -= modul; + } + return pt; +} + +/// Returns YES if and only if value is contained in the intervall [pt-range/2, pt+range/2] +/// all calculations are done in R/(modul*N) +/// or equivalently: x and (x+modul) are treated as equivalent values forall x in R +/// (R=real numbers, N=natural numbers) +static BOOL rangeContainsValueModulo(double pt, double range, double value, double modul) +{ + double halfRange = range * 0.5; + pt = modulo(pt, modul); + double start = modulo(pt-halfRange, modul); + double end = modulo(pt+halfRange, modul); + if(start <= end) { + return start <= value && value <= end; + } else { + return end <= value || value <= start; + } + return NO; +} + +BOOL MKCoordinateRegionContainsPoint(MKCoordinateRegion region, CLLocationCoordinate2D pt) +{ + return rangeContainsValueModulo(region.center.longitude, region.span.longitudeDelta, pt.longitude, 360.0) && + rangeContainsValueModulo(region.center.latitude, region.span.latitudeDelta, pt.latitude, 180.0); +} + +BOOL MKCoordinateRegionContainsRegion(MKCoordinateRegion a, MKCoordinateRegion b) +{ + double bHalfLatDelta = b.span.latitudeDelta*0.5; + double bHalfLonDelta = b.span.longitudeDelta*0.5; + CLLocationCoordinate2D b_northWest = CLLocationCoordinate2DMake(b.center.latitude-bHalfLatDelta, b.center.longitude-bHalfLonDelta); + CLLocationCoordinate2D b_southEast = CLLocationCoordinate2DMake(b.center.latitude+bHalfLatDelta, b.center.longitude+bHalfLonDelta); + if(!MKCoordinateRegionContainsPoint(a, b_northWest)) + return NO; + if(!MKCoordinateRegionContainsPoint(a, b_southEast)) + return NO; + + return YES; +} diff --git a/Anyway/OCMapView/OCGrouping.h b/Anyway/OCMapView/OCGrouping.h new file mode 100755 index 0000000..d98e5f3 --- /dev/null +++ b/Anyway/OCMapView/OCGrouping.h @@ -0,0 +1,16 @@ +// +// OCGrouping.h +// OClusterMapView+Sample +// +// Created by Botond Kis on 13.02.12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import + +/// Protocol which is needed to use different groups of clusters +/** Implement this protocol in an annotation to enable clustering of groups + */ +@protocol OCGrouping +@property (nonatomic, readonly, copy) NSString *groupTag; +@end diff --git a/Anyway/OCMapView/OCMapView.h b/Anyway/OCMapView/OCMapView.h new file mode 100755 index 0000000..e34b203 --- /dev/null +++ b/Anyway/OCMapView/OCMapView.h @@ -0,0 +1,88 @@ +// +// OCMapView.h +// openClusterMapView +// +// Created by Botond Kis on 14.07.11. +// + +#import + +#import "OCDistance.h" +#import "OCAnnotation.h" +#import "OCAlgorithms.h" + +/// MapView replacement for MKMapView +/** OCMapView works like the standard MKMapView but + creates clusters from all added annotations.*/ +@interface OCMapView : MKMapView + +// +/// List of annotations which will be ignored by the clustering algorithm. +/** The objects in this array must adopt the @see MKAnnotation protocol. + The clustering algorithms will automatically ignore this annotations.*/ +@property(nonatomic, strong) NSMutableSet *annotationsToIgnore; + +/// The complete list of annotations displayed on the map including clusters (read-only). +/// The objects in this array adopt the @see MKAnnotation protocol. +/// It contains all annotations as they are on the MapView. +@property(nonatomic, readonly) NSArray *displayedAnnotations; + +/// Enables or disables clustering. +@property(nonatomic, assign) BOOL clusteringEnabled; + +// +/// Defines the clustering algorithm which should be used. +/** @see OCClusteringMethod for more information + + default: OCClusteringMethodBubble*/ +@property(nonatomic, assign) OCClusteringMethod clusteringMethod; + + +// +/// Defines the cluster size in units of the map width. +/** eg. clusterSize 0.5 is the half of the map. +default: 0.2*/ +@property(nonatomic, assign) float clusterSize; + +// +/// Enables multiple clusters +/** If enabled, tha mapview will generate different clusters for Tags + implemented by the OCGrouping protocol. + default: NO*/ +@property(nonatomic, assign) BOOL clusterByGroupTag; + +// +/// Defines the "zoom" from where the map should start clustering. +/** If the map is zoomed below this value it won't cluster. + default: 0.0 (no min. zoom)*/ +@property(nonatomic, assign) CLLocationDegrees minLongitudeDeltaToCluster; + +// +/// Defines how many annotations are needed to build a cluster +/** If a cluster contains less annotations, they will shown as they are + default: 0 (no minimum count)*/ +@property(nonatomic, assign) NSUInteger minimumAnnotationCountPerCluster; + +// +/// Clusters all annotations, even if they are outside of the visible MKCoordinateRegion +/* default: NO (checks for boundaries)*/ +@property (nonatomic, assign) BOOL clusterInvisibleViews; + +/// Handles the ignoreList of annotations, calls the defined clustering +/// algorithm and adds the clustered annotations to the map. +- (void)doClustering; + +#pragma mark - Helpers + +/** + * Remove any annotations that will not be visible on the map + * + * @param annotationsToFilter array of id + * + * @return array of filtered annotations + */ +- (NSArray *)filterAnnotationsForVisibleMap:(NSArray *)annotationsToFilter; + +@end + + diff --git a/Anyway/OCMapView/OCMapView.m b/Anyway/OCMapView/OCMapView.m new file mode 100755 index 0000000..e3f1ff4 --- /dev/null +++ b/Anyway/OCMapView/OCMapView.m @@ -0,0 +1,263 @@ +// +// OCMapView.m +// openClusterMapView +// +// Created by Botond Kis on 14.07.11. +// + +#import "OCMapView.h" + + + +@implementation OCMapView +{ + BOOL _clusteringEnabled; + NSMutableSet *_allAnnotations; + MKCoordinateRegion _lastRefreshedMapRegion; + MKMapRect _lastRefreshedMapRect; + NSArray *_reclusterOnChangeProperties; + + NSTimer *_doClusteringTimer; +} + +@synthesize clusteringEnabled = _clusteringEnabled; + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + [self sharedInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + if (self) { + [self sharedInit]; + } + return self; +} + +- (void)sharedInit +{ + _allAnnotations = [[NSMutableSet alloc] init]; + _annotationsToIgnore = [[NSMutableSet alloc] init]; + _clusteringMethod = OCClusteringMethodBubble; + _clusterSize = 0.2; + _minLongitudeDeltaToCluster = 0.0; + _minimumAnnotationCountPerCluster = 0; + _clusteringEnabled = YES; + _clusterByGroupTag = NO; + _clusterInvisibleViews = NO; + + // define relevant properties (those, which will affect the clustering) + _reclusterOnChangeProperties = @[@"annotationsToIgnore", + @"clusteringEnabled", + @"clusteringMethod", + @"clusterSize", + @"clusterByGroupTag", + @"minLongitudeDeltaToCluster", + @"minimumAnnotationCountPerCluster", + @"clusterInvisibleViews", + @"annotationsToIgnore"]; + + // listen to changes + for (NSString *keyPath in _reclusterOnChangeProperties) { + [self addObserver:self forKeyPath:keyPath + options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; + } +} + +- (void)dealloc +{ + if(_doClusteringTimer) { + [_doClusteringTimer invalidate]; + _doClusteringTimer = nil; + } + for (NSString *keyPath in _reclusterOnChangeProperties) { + [self removeObserver:self forKeyPath:keyPath]; + } +} + +#pragma mark - MKMapView + +- (void)addAnnotation:(id < MKAnnotation >)annotation { + [_allAnnotations addObject:annotation]; + [self doClustering]; +} + +- (void)addAnnotations:(NSArray *)annotations { + [_allAnnotations addObjectsFromArray:annotations]; + [self doClustering]; +} + +- (void)removeAnnotation:(id < MKAnnotation >)annotation { + [_allAnnotations removeObject:annotation]; + [self doClustering]; +} + +- (void)removeAnnotations:(NSArray *)annotations{ + for (id annotation in annotations) { + [_allAnnotations removeObject:annotation]; + } + [self doClustering]; +} + +#pragma mark - Properties +// +// Returns, like the original method, +// all annotations in the map unclustered. +- (NSArray *)annotations { + return [_allAnnotations allObjects]; +} + +// +// Returns all annotations which are actually displayed on the map. (clusters) +- (NSArray *)displayedAnnotations { + return super.annotations; +} + +// +// Observe properties, that will need reclustering on change +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context; +{ + if ([_reclusterOnChangeProperties containsObject:keyPath]) { + if (![[change objectForKey:NSKeyValueChangeNewKey] + isEqual:[change objectForKey:NSKeyValueChangeOldKey]]) + { + [self doClustering]; + } + } +} + +#pragma mark - Clustering + +- (void)doClustering +{ + if(!_doClusteringTimer) { + _doClusteringTimer = [NSTimer scheduledTimerWithTimeInterval:0.0 + target:self + selector:@selector(doClusteringNow) + userInfo:nil + repeats:NO]; + } +} + +- (void)doClusteringNow +{ + [_doClusteringTimer invalidate]; + _doClusteringTimer = nil; + + NSMutableArray *annotationsToCluster = nil; + MKCoordinateRegion self_region = self.region; + + // Filter invisible (eg. out of visible map rect) annotations, if wanted + if(self.clusterInvisibleViews) { + annotationsToCluster = [[_allAnnotations allObjects] mutableCopy]; + } else { + annotationsToCluster = [[self filterAnnotationsForVisibleMap:[_allAnnotations allObjects]] mutableCopy]; + } + + // Remove the annotation which should be ignored + [annotationsToCluster removeObjectsInArray:[_annotationsToIgnore allObjects]]; + + // Cluster annotations, when enabled and map is above the minimum zoom + NSArray *clusteredAnnotations; + if (_clusteringEnabled && (self_region.span.longitudeDelta > _minLongitudeDeltaToCluster)) + { + //calculate cluster radius + CLLocationDistance clusterRadius = self_region.span.longitudeDelta * _clusterSize; + + // clustering + if (self.clusteringMethod == OCClusteringMethodBubble) { + clusteredAnnotations = [OCAlgorithms bubbleClusteringWithAnnotations:annotationsToCluster + clusterRadius:clusterRadius + grouped:self.clusterByGroupTag]; + } else { + clusteredAnnotations = [OCAlgorithms gridClusteringWithAnnotations:annotationsToCluster + clusterRect:MKCoordinateSpanMake(clusterRadius, clusterRadius) + grouped:self.clusterByGroupTag]; + } + } + // pass through without when not + else{ + clusteredAnnotations = annotationsToCluster; + } + + NSMutableArray *annotationsToDisplay = [clusteredAnnotations mutableCopy]; + [annotationsToDisplay addObjectsFromArray:[_annotationsToIgnore allObjects]]; + + // check minumum cluster size + for (NSInteger i=0; i annotation in self.displayedAnnotations) { + if (annotation == self.userLocation) { + continue; + } + + // remove old annotations + if (![annotationsToDisplay containsObject:annotation]) { + [super removeAnnotation:annotation]; + } else { + [annotationsToDisplay removeObject:annotation]; + } + } + + // add not existing annotations + [super addAnnotations:annotationsToDisplay]; + + // update last rects & needs clustering + _lastRefreshedMapRect = self.visibleMapRect; + _lastRefreshedMapRegion = self.region; +} + +#pragma mark map rect changes tracking + +- (BOOL)mapWasZoomed +{ + return (fabs(_lastRefreshedMapRect.size.width - self.visibleMapRect.size.width) > 0.1f); +} + +- (BOOL)mapWasPannedSignificantly +{ + CGPoint lastPoint = [self convertCoordinate:_lastRefreshedMapRegion.center toPointToView:self]; + CGPoint currentPoint = [self convertCoordinate:self.region.center toPointToView:self]; + + return ((fabs(lastPoint.x - currentPoint.x) > self.frame.size.width/3.0) || + (fabs(lastPoint.y - currentPoint.y) > self.frame.size.height/3.0)); +} + +#pragma mark - Helpers + +- (NSArray *)filterAnnotationsForVisibleMap:(NSArray *)annotationsToFilter +{ + NSMutableArray *filteredAnnotations = [[NSMutableArray alloc] initWithCapacity:[annotationsToFilter count]]; + + MKCoordinateRegion self_region = self.region; + + for (id annotation in annotationsToFilter) + { + // if annotation is not inside the coordinates, kick it + if(MKCoordinateRegionContainsPoint(self_region, [annotation coordinate])) { + [filteredAnnotations addObject:annotation]; + } + } + + return filteredAnnotations; +} + +@end diff --git a/Anyway/PrintlnMagic.swift b/Anyway/PrintlnMagic.swift new file mode 100755 index 0000000..075c175 --- /dev/null +++ b/Anyway/PrintlnMagic.swift @@ -0,0 +1,20 @@ +// +// PrintlnMagic.swift +// +// Created by Arthur Sabintsev on 1/28/15. +// Copyright (c) 2015 Arthur Ariel Sabintsev. All rights reserved. +// + +import Foundation + +/** + Overrides Swift's default println() implementation. + + As with the original println() function, this function writes the textual representation of `object` into the standard output. + It augments the original function with the filename, function name, and line number of the object that is being logged. +*/ +public func println(object: T, _ file: String = __FILE__, _ function: String = __FUNCTION__, _ line: Int = __LINE__) +{ + let filename = (file as NSString).lastPathComponent.stringByReplacingOccurrencesOfString(".swift", withString: "") + print("\(filename).\(function)[\(line)]: \(object)\n", terminator: "") +} diff --git a/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.h b/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.h new file mode 100755 index 0000000..b18e258 --- /dev/null +++ b/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.h @@ -0,0 +1,323 @@ +// +// RMDateSelectionViewController.h +// RMDateSelectionViewController +// +// Created by Roland Moers on 26.10.13. +// Copyright (c) 2013 Roland Moers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import + +typedef NS_ENUM(NSInteger, RMDateSelectionViewControllerStatusBarHiddenMode) { + /** On iOS 7, the status bar is not hidden in any orientation. On iOS 8, the status is not hidden in portrait mode and hidden in landscape mode. */ + RMDateSelectionViewControllerStatusBarHiddenModeDefault, + /** The status bar is always hidden, regardless of orientation and iOS version. */ + RMDateSelectionViewControllerStatusBarHiddenModeAlways, + /** The status bar is never hidden, regardless of orientation and iOS version. */ + RMDateSelectionViewControllerStatusBarHiddenModeNever +}; + +@class RMDateSelectionViewController; + +/** + This block is called when the user selects a certain date if blocks are used. + + @param vc The date selection view controller that just finished selecting a date. + @param aDate The selected date. + */ +typedef void (^RMDateSelectionBlock)(RMDateSelectionViewController *vc, NSDate *aDate); + +/** + This block is called when the user cancels if blocks are used. + + @param vc The date selection view controller that just got canceled. + */ +typedef void (^RMDateCancelBlock)(RMDateSelectionViewController *vc); + +/** + * These methods are used to inform the [delegate]([RMDateSelectionViewController delegate]) of an instance of RMDateSelectionViewController about the status of the date selection view controller. + */ +@protocol RMDateSelectionViewControllerDelegate + +/// @name Cancel and Select + +/** + This method is called when the user selects a certain date. + + @param vc The date selection view controller that just finished selecting a date. + @param aDate The selected date. + */ +- (void)dateSelectionViewController:(RMDateSelectionViewController *)vc didSelectDate:(NSDate *)aDate; + +@optional + +/** + This method is called when the user selects the cancel button or taps the darkened background (if the property [backgroundTapsDisabled]([RMDateSelectionViewController backgroundTapsDisabled]) of RMDateSelectionViewController returns NO). + + @discussion Implementation of this method is optional. When the cancel button is pressed, the date selection view controller will be dismissed. This method can be implemented to do anything additional to the dismissal. + + @param vc The date selection view controller that just canceled. + */ +- (void)dateSelectionViewControllerDidCancel:(RMDateSelectionViewController *)vc; + +/// @name Additional Buttons + +/** + * This method is called when the now button of the date selection view controller has been pressed. + * + * @warning Implementation of this method is optional. If you choose to implement it, you are responsible to do whatever should be done when the now button has been pressed. If you do not choose to implement it, the default behavior is to set the date selection control to the current date. + * + * @param vc The date selection view controller whose now button has been pressed. + */ +- (void)dateSelectionViewControllerNowButtonPressed:(RMDateSelectionViewController *)vc; + +@end + +/** + * RMDateSelectionViewController is an iOS control for selecting a date using UIDatePicker in a UIActionSheet like fashon. When a RMDateSelectionViewController is shown the user gets the opportunity to select a date using a UIDatePicker. + * + * RMDateSelectionViewController supports bouncing effects when animating the date selection view controller. In addition, motion effects are supported while showing the date selection view controller. Both effects can be disabled by using the properties called disableBouncingWhenShowing and disableMotionEffects. + * + * On iOS 8 and later Apple opened up their API for blurring the background of UIViews. RMDateSelectionViewController makes use of this API. The type of the blur effect can be changed by using the blurEffectStyle property. If you want to disable the blur effect you can do so by using the disableBlurEffects property. + * + * @warning RMDateSelectionViewController is not designed to be reused. Each time you want to display a RMDateSelectionViewController a new instance should be created. If you want to set a specific date before displaying, you can do so by using the datePicker property. + */ +@interface RMDateSelectionViewController : UIViewController + +/// @name Getting an Instance + +/** + * This returns a new instance of RMDateSelectionViewController. + * + * @warning Always use this class method to get an instance. Do not initialize an instance yourself. + * + * @return Returns a new instance of RMDateSelectionViewController + */ ++ (instancetype)dateSelectionController; + +/// @name Localization + +/** + * Set a localized title for the now button. Default title is 'Now'. + * + * @param newLocalizedTitle The new localized title for the now button. + */ ++ (void)setLocalizedTitleForNowButton:(NSString *)newLocalizedTitle; + +/** + * Set a localized title for the cancel button. Default title is 'Cancel'. + * + * @param newLocalizedTitle The new localized title for the cancel button. + */ ++ (void)setLocalizedTitleForCancelButton:(NSString *)newLocalizedTitle; + +/** + * Set a localized title for the select button. Default is 'Select'. + * + * @param newLocalizedTitle The new localized title for the select button. + */ ++ (void)setLocalizedTitleForSelectButton:(NSString *)newLocalizedTitle; + +/** + * Set a image for the select button. Default is nil. + * + * @param newImage The new image for the select button. + */ ++ (void)setImageForSelectButton:(UIImage *)newImage; + +/** + * Set a image for the cancel button. Default is nil. + * + * @param newImage The new image for the cancel button. + */ ++ (void)setImageForCancelButton:(UIImage *)newImage; + +/// @name Delegate + +/** + * Used to set the delegate. + * + * The delegate must conform to the RMDateSelectionViewControllerDelegate protocol. + */ +@property (weak) id delegate; + +/// @name User Interface + +/** + * Will return the instance of UIDatePicker that is used. + */ +@property (nonatomic, readonly) UIDatePicker *datePicker; + +/** + * Will return the label that is used as a title for the picker. You can use this property to set a title and to customize the appearance of the title. + * + * @warning If you want to set a title, be sure to set it before showing the picker view controller as otherwise the title will not be shown. + */ +@property (nonatomic, strong, readonly) UILabel *titleLabel; + +/** + * When YES the now button is hidden. Default value is NO. + * + * @warning If you want to change this property you must do this before showing the RMDateSelectionViewController or otherwise setting this property has no effect. + */ +@property (assign, nonatomic) BOOL hideNowButton; + +/** + * When YES taps on the background view are ignored. Default value is NO. + */ +@property (assign, nonatomic) BOOL backgroundTapsDisabled; + +/// @name Appearance + +/** + * Used to set the preferred status bar style. + */ +@property (nonatomic, assign, readwrite) UIStatusBarStyle preferredStatusBarStyle; + +/** + * Used to hide the status bar. + */ +@property (nonatomic, assign) RMDateSelectionViewControllerStatusBarHiddenMode statusBarHiddenMode; + +/** + * Used to set the text color of the buttons but not the date picker. + */ +@property (strong, nonatomic) UIColor *tintColor; + +/** + * Used to set the background color. + */ +@property (strong, nonatomic) UIColor *backgroundColor; + +/** + * Used to set the background color when the user selets a button. + */ +@property (strong, nonatomic) UIColor *selectedBackgroundColor; + +/// @name Effects + +/** + * Used to enable or disable motion effects. Default value is NO. + * + * @warning This property always returns YES, if motion is reduced via accessibilty options. + */ +@property (assign, nonatomic) BOOL disableMotionEffects; + +/** + * Used to enable or disable bouncing effects when sliding in the date selection view. Default value is NO. + * + * @warning This property always returns YES, if motion is reduced via accessibilty options. + */ +@property (assign, nonatomic) BOOL disableBouncingWhenShowing; + +/** + * Used to enable or disable blurring the date selection view. Default value is NO. + * + * @warning This property always returns YES if either UIBlurEffect, UIVibrancyEffect or UIVisualEffectView is not available on your system at runtime or transparency is reduced via accessibility options. + */ +@property (assign, nonatomic) BOOL disableBlurEffects; + +/** + * Used to choose a particular blur effect style (default value is UIBlurEffectStyleExtraLight). The value ir ignored if blur effects are disabled. + */ +@property (assign, nonatomic) UIBlurEffectStyle blurEffectStyle; + +/// @name Showing + +/** + * This shows the date selection view controller on top of every other view controller using a new UIWindow. The RMDateSelectionViewController will be added as a child view controller of the UIWindows root view controller. The background of the root view controller is used to darken the views behind the RMDateSelectionViewController. + * + * This method is the preferred method for showing a RMDateSelectionViewController on iPhones and iPads. Nevertheless, there are situations where this method is not sufficient on iPads. An example for this is that the RMDateSelectionViewController shall be shown within an UIPopover. This can be achieved by using [showFromViewController:]([RMDateSelectionViewController showFromViewController:]). + * + * @warning Make sure the delegate property is assigned. Otherwise you will not get any calls when a date is selected or the selection has been canceled. + */ +- (void)show; + +/** + * This shows the date selection view controller on top of every other view controller using a new UIWindow. The RMDateSelectionViewController will be added as a child view controller of the UIWindows root view controller. The background of the root view controller is used to darken the views behind the RMDateSelectionViewController. + * + * After a date has been selected the selection block will be called. If the user choses to cancel the selection, the cancel block will be called. If you assigned a delegate the corresponding methods will be called, too. + * + * This method is the preferred method for showing a RMDateSelectionViewController on iPhones and iPads when a block based API is preferred. Nevertheless, there are situations where this method is not sufficient on iPads. An example for this is that the RMDateSelectionViewController shall be shown within an UIPopover. This can be achieved by using [showFromViewController:withSelectionHandler:andCancelHandler:]([RMDateSelectionViewController showFromViewController:withSelectionHandler:andCancelHandler:]). + * + * @param selectionBlock The block to call when the user selects a date. + * @param cancelBlock The block to call when the user cancels the selection. + */ +- (void)showWithSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock; + +/** + * This shows the date selection view controller as child view controller of the view controller you passed in as parameter. The content of this view controller will be darkened and the date selection view controller will be shown on top. + * + * @warning This method should only be used on iPads in situations where [show]([RMDateSelectionViewController show]) is not sufficient (for example, when the RMDateSelectionViewController shoud be shown within an UIPopover). If [show]([RMDateSelectionViewController show]) is sufficient, please use it! + * + * @warning Make sure the delegate property is assigned. Otherwise you will not get any calls when a date is selected or the selection has been canceled. + * + * @param aViewController The parent view controller of the RMDateSelectionViewController. + */ +- (void)showFromViewController:(UIViewController *)aViewController; + +/** + * This shows the date selection view controller as child view controller of the view controller you passed in as parameter. The content of this view controller will be darkened and the date selection view controller will be shown on top. + * + * After a date has been selected the selection block will be called. If the user choses to cancel the selection, the cancel block will be called. If you assigned a delegate the corresponding methods will be called, too. + * + * @warning This method should only be used on iPads in situations where [showWithSelectionHandler:andCancelHandler:]([RMDateSelectionViewController showWithSelectionHandler:andCancelHandler:]) is not sufficient (for example, when the RMDateSelectionViewController shoud be shown within an UIPopover). If [showWithSelectionHandler:andCancelHandler:]([RMDateSelectionViewController showWithSelectionHandler:andCancelHandler:]) is sufficient, please use it! + * + * @param aViewController The parent view controller of the RMDateSelectionViewController. + * @param selectionBlock The block to call when the user selects a date. + * @param cancelBlock The block to call when the user cancels the selection. + */ +- (void)showFromViewController:(UIViewController *)aViewController withSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock; + +/** + * This shows the date selection view controller within a popover. The popover is initialized with the date selection view controller as content view controller and then presented from the rect in the view given as parameters. + * + * @warning Make sure the delegate property is assigned. Otherwise you will not get any calls when a date is selected or the selection has been canceled. + * + * @warning This method should only be used on iPads. On iPhones please use [show]([RMDateSelectionViewController show]) or [showWithSelectionHandler:andCancelHandler:]([RMDateSelectionViewController showWithSelectionHandler:andCancelHandler:]) instead. + * + * @param aRect The rect in the given view the popover should be presented from. + * @param aView The view the popover should be presented from. + */ +- (void)showFromRect:(CGRect)aRect inView:(UIView *)aView; + +/** + * This shows the date selection view controller within a popover. The popover is initialized with the date selection view controller as content view controller and then presented from the rect in the view given as parameters. + * + * After a date has been selected the selection block will be called. If the user choses to cancel the selection, the cancel block will be called. If you assigned a delegate the corresponding methods will be called, too. + * + * @warning This method should only be used on iPads. On iPhones please use [show]([RMDateSelectionViewController show]) or [showWithSelectionHandler:andCancelHandler:]([RMDateSelectionViewController showWithSelectionHandler:andCancelHandler:]) instead. + * + * @param aRect The rect in the given view the popover should be presented from. + * @param aView The view the popover should be presented from. + * @param selectionBlock The block to call when the user selects a date. + * @param cancelBlock The block to call when the user cancels the selection. + */ +- (void)showFromRect:(CGRect)aRect inView:(UIView *)aView withSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock; + +/// @name Dismissing + +/** + * This will dismiss the date selection view controller and remove it from the view hierarchy. + */ +- (void)dismiss; + +@end diff --git a/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.m b/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.m new file mode 100755 index 0000000..199568b --- /dev/null +++ b/Anyway/RMDateSelectionViewController/RMDateSelectionViewController.m @@ -0,0 +1,1006 @@ +// +// RMDateSelectionViewController.m +// RMDateSelectionViewController +// +// Created by Roland Moers on 26.10.13. +// Copyright (c) 2013 Roland Moers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import "RMDateSelectionViewController.h" +#import + +@interface NSDate (Rounding) + +- (NSDate *)dateByRoundingToMinutes:(NSInteger)minutes; + +@end + +@implementation NSDate (Rounding) + +- (NSDate *)dateByRoundingToMinutes:(NSInteger)minutes { + NSTimeInterval absoluteTime = floor([self timeIntervalSinceReferenceDate]); + NSTimeInterval minuteInterval = minutes*60; + + NSTimeInterval remainder = (absoluteTime - (floor(absoluteTime/minuteInterval)*minuteInterval)); + if(remainder < 60) { + return self; + } else { + NSTimeInterval remainingSeconds = minuteInterval - remainder; + return [self dateByAddingTimeInterval:remainingSeconds]; + } +} + +@end + +/* + * We need RMNonRotatingDateSelectionViewController because Apple decided that a UIWindow adds a black background while rotating. + * ( http://stackoverflow.com/questions/19782944/blacked-out-interface-rotation-when-using-second-uiwindow-with-rootviewcontrolle ) + * + * To work around this problem, the root view controller of our window is a RMNonRotatingDateSelectionViewController which cannot rotate. + * In this case, UIWindow does not add a black background (as it is not rotating any more) and we handle the rotation + * ourselves. + */ +@interface RMNonRotatingDateSelectionViewController : UIViewController + +@property (nonatomic, assign) UIInterfaceOrientation mutableInterfaceOrientation; +@property (nonatomic, assign, readwrite) UIStatusBarStyle preferredStatusBarStyle; +@property (nonatomic, assign) RMDateSelectionViewControllerStatusBarHiddenMode statusBarHiddenMode; + +@end + +@implementation RMNonRotatingDateSelectionViewController + +#pragma mark - Init and Dealloc +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRotate) name:UIDeviceOrientationDidChangeNotification object:nil]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + + [super viewDidDisappear:animated]; +} + +#pragma mark - Orientation +- (BOOL)shouldAutorotate { + return NO; +} + +- (void)didRotate { + [self updateUIForInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation animated:YES]; + + [self setNeedsStatusBarAppearanceUpdate]; +} + +- (void)updateUIForInterfaceOrientation:(UIInterfaceOrientation)newOrientation animated:(BOOL)animated { + CGFloat duration = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad ? 0.4f : 0.3f); + BOOL doubleDuration = NO; + + CGFloat angle = 0.f; + CGRect screenBounds = [[UIScreen mainScreen] bounds]; + + if(newOrientation == UIInterfaceOrientationPortrait) { + angle = 0; + if(self.mutableInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) + doubleDuration = YES; + } else if(newOrientation == UIInterfaceOrientationPortraitUpsideDown) { + angle = M_PI; + if(self.mutableInterfaceOrientation == UIInterfaceOrientationPortrait) + doubleDuration = YES; + } else if(newOrientation == UIInterfaceOrientationLandscapeLeft) { + angle = -M_PI_2; + if(self.mutableInterfaceOrientation == UIInterfaceOrientationLandscapeRight) + doubleDuration = YES; + } else if(newOrientation == UIInterfaceOrientationLandscapeRight) { + angle = M_PI_2; + if(self.mutableInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) + doubleDuration = YES; + } + + if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 && UIInterfaceOrientationIsLandscape(newOrientation) && animated) { + screenBounds = CGRectMake(0, 0, screenBounds.size.height, screenBounds.size.width); + } + + if(animated) { + __weak RMNonRotatingDateSelectionViewController *blockself = self; + [UIView animateWithDuration:(doubleDuration ? duration*2 : duration) delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + blockself.view.transform = CGAffineTransformMakeRotation(angle); + blockself.view.frame = screenBounds; + } completion:^(BOOL finished) { + }]; + } else { + self.view.transform = CGAffineTransformMakeRotation(angle); + self.view.frame = screenBounds; + } + + self.mutableInterfaceOrientation = [UIApplication sharedApplication].statusBarOrientation; +} + +#pragma mark - Status Bar +- (BOOL)prefersStatusBarHidden { + if(self.statusBarHiddenMode == RMDateSelectionViewControllerStatusBarHiddenModeNever) { + return NO; + } else if(self.statusBarHiddenMode == RMDateSelectionViewControllerStatusBarHiddenModeAlways) { + return YES; + } else { + if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { + if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) + return YES; + + return NO; + } else { + return NO; + } + } +} + +@end + +#define RM_DATE_PICKER_HEIGHT_PORTRAIT 216 +#define RM_DATE_PICKER_HEIGHT_LANDSCAPE 162 + +typedef enum { + RMDateSelectionViewControllerPresentationTypeWindow, + RMDateSelectionViewControllerPresentationTypeViewController, + RMDateSelectionViewControllerPresentationTypePopover +} RMDateSelectionViewControllerPresentationType; + +@interface RMDateSelectionViewController () + +@property (nonatomic, assign) RMDateSelectionViewControllerPresentationType presentationType; +@property (nonatomic, strong) UIWindow *window; +@property (nonatomic, strong) UIViewController *rootViewController; +@property (nonatomic, strong) UIView *backgroundView; +@property (nonatomic, strong) UIPopoverController *popover; + +@property (nonatomic, weak) NSLayoutConstraint *xConstraint; +@property (nonatomic, weak) NSLayoutConstraint *yConstraint; +@property (nonatomic, weak) NSLayoutConstraint *widthConstraint; + +@property (nonatomic, strong) UIView *titleLabelContainer; +@property (nonatomic, strong, readwrite) UILabel *titleLabel; + +@property (nonatomic, strong) UIView *nowButtonContainer; +@property (nonatomic, strong) UIButton *nowButton; + +@property (nonatomic, strong) UIView *datePickerContainer; +@property (nonatomic, readwrite, strong) UIDatePicker *datePicker; +@property (nonatomic, strong) NSLayoutConstraint *pickerHeightConstraint; + +@property (nonatomic, strong) UIView *cancelAndSelectButtonContainer; +@property (nonatomic, strong) UIView *cancelAndSelectButtonSeperator; +@property (nonatomic, strong) UIButton *cancelButton; +@property (nonatomic, strong) UIButton *selectButton; + +@property (nonatomic, strong) UIMotionEffectGroup *motionEffectGroup; + +@property (nonatomic, copy) RMDateSelectionBlock selectedDateBlock; +@property (nonatomic, copy) RMDateCancelBlock cancelBlock; + +@property (nonatomic, assign) BOOL hasBeenDismissed; + +@end + +@implementation RMDateSelectionViewController + +@synthesize selectedBackgroundColor = _selectedBackgroundColor; +@synthesize disableMotionEffects = _disableMotionEffects; + +#pragma mark - Class ++ (instancetype)dateSelectionController { + return [[RMDateSelectionViewController alloc] init]; +} + +static NSString *_localizedNowTitle = @"Now"; +static NSString *_localizedCancelTitle = @"Cancel"; +static NSString *_localizedSelectTitle = @"Select"; +static UIImage *_selectImage; +static UIImage *_cancelImage; + ++ (NSString *)localizedTitleForNowButton { + return _localizedNowTitle; +} + ++ (NSString *)localizedTitleForCancelButton { + return _localizedCancelTitle; +} + ++ (NSString *)localizedTitleForSelectButton { + return _localizedSelectTitle; +} + ++ (void)setLocalizedTitleForNowButton:(NSString *)newLocalizedTitle { + _localizedNowTitle = newLocalizedTitle; +} + ++ (void)setLocalizedTitleForCancelButton:(NSString *)newLocalizedTitle { + _localizedCancelTitle = newLocalizedTitle; +} + ++ (void)setLocalizedTitleForSelectButton:(NSString *)newLocalizedTitle { + _localizedSelectTitle = newLocalizedTitle; +} + ++ (UIImage *)imageForSelectButton { + return _selectImage; +} + ++ (UIImage *)imageForCancelButton { + return _cancelImage; +} + ++ (void)setImageForSelectButton:(UIImage *)newImage { + _selectImage = newImage; +} + ++ (void)setImageForCancelButton:(UIImage *)newImage { + _cancelImage = newImage; +} + ++ (void)showDateSelectionViewController:(RMDateSelectionViewController *)aDateSelectionViewController animated:(BOOL)animated { + if(aDateSelectionViewController.presentationType == RMDateSelectionViewControllerPresentationTypeWindow) { + [(RMNonRotatingDateSelectionViewController *)aDateSelectionViewController.rootViewController updateUIForInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation animated:NO]; + [aDateSelectionViewController.window makeKeyAndVisible]; + + // If we start in landscape mode also update the windows frame to be accurate + if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + aDateSelectionViewController.window.frame = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width); + } + } + + if(aDateSelectionViewController.presentationType != RMDateSelectionViewControllerPresentationTypePopover) { + aDateSelectionViewController.backgroundView.alpha = 0; + [aDateSelectionViewController.rootViewController.view addSubview:aDateSelectionViewController.backgroundView]; + + [aDateSelectionViewController.rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:aDateSelectionViewController.backgroundView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; + [aDateSelectionViewController.rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:aDateSelectionViewController.backgroundView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]]; + [aDateSelectionViewController.rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:aDateSelectionViewController.backgroundView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; + [aDateSelectionViewController.rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:aDateSelectionViewController.backgroundView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeHeight multiplier:1 constant:0]]; + } + + [aDateSelectionViewController willMoveToParentViewController:aDateSelectionViewController.rootViewController]; + [aDateSelectionViewController viewWillAppear:YES]; + + [aDateSelectionViewController.rootViewController addChildViewController:aDateSelectionViewController]; + [aDateSelectionViewController.rootViewController.view addSubview:aDateSelectionViewController.view]; + + [aDateSelectionViewController viewDidAppear:YES]; + [aDateSelectionViewController didMoveToParentViewController:aDateSelectionViewController.rootViewController]; + + //CGFloat height = RM_DATE_SELECTION_VIEW_HEIGHT_PORTAIT; + if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { + if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + //height = RM_DATE_SELECTION_VIEW_HEIGHT_LANDSCAPE; + aDateSelectionViewController.pickerHeightConstraint.constant = RM_DATE_PICKER_HEIGHT_LANDSCAPE; + } else { + //height = RM_DATE_SELECTION_VIEW_HEIGHT_PORTAIT; + aDateSelectionViewController.pickerHeightConstraint.constant = RM_DATE_PICKER_HEIGHT_PORTRAIT; + } + } + + aDateSelectionViewController.xConstraint = [NSLayoutConstraint constraintWithItem:aDateSelectionViewController.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; + aDateSelectionViewController.yConstraint = [NSLayoutConstraint constraintWithItem:aDateSelectionViewController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; + aDateSelectionViewController.widthConstraint = [NSLayoutConstraint constraintWithItem:aDateSelectionViewController.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; + + [aDateSelectionViewController.rootViewController.view addConstraint:aDateSelectionViewController.xConstraint]; + [aDateSelectionViewController.rootViewController.view addConstraint:aDateSelectionViewController.yConstraint]; + [aDateSelectionViewController.rootViewController.view addConstraint:aDateSelectionViewController.widthConstraint]; + + [aDateSelectionViewController.rootViewController.view setNeedsUpdateConstraints]; + [aDateSelectionViewController.rootViewController.view layoutIfNeeded]; + + [aDateSelectionViewController.rootViewController.view removeConstraint:aDateSelectionViewController.yConstraint]; + aDateSelectionViewController.yConstraint = [NSLayoutConstraint constraintWithItem:aDateSelectionViewController.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeBottom multiplier:1 constant:-10]; + [aDateSelectionViewController.rootViewController.view addConstraint:aDateSelectionViewController.yConstraint]; + + [aDateSelectionViewController.rootViewController.view setNeedsUpdateConstraints]; + + if(animated) { + CGFloat damping = 1.0f; + CGFloat duration = 0.3f; + if(!aDateSelectionViewController.disableBouncingWhenShowing) { + damping = 0.6f; + duration = 1.0f; + } + + [UIView animateWithDuration:duration delay:0 usingSpringWithDamping:damping initialSpringVelocity:1 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{ + aDateSelectionViewController.backgroundView.alpha = 1; + + [aDateSelectionViewController.rootViewController.view layoutIfNeeded]; + } completion:^(BOOL finished) { + }]; + } else { + aDateSelectionViewController.backgroundView.alpha = 0; + + [aDateSelectionViewController.rootViewController.view layoutIfNeeded]; + } +} + ++ (void)dismissDateSelectionViewController:(RMDateSelectionViewController *)aDateSelectionViewController { + [aDateSelectionViewController.rootViewController.view removeConstraint:aDateSelectionViewController.yConstraint]; + aDateSelectionViewController.yConstraint = [NSLayoutConstraint constraintWithItem:aDateSelectionViewController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:aDateSelectionViewController.rootViewController.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; + [aDateSelectionViewController.rootViewController.view addConstraint:aDateSelectionViewController.yConstraint]; + + [aDateSelectionViewController.rootViewController.view setNeedsUpdateConstraints]; + + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + aDateSelectionViewController.backgroundView.alpha = 0; + + [aDateSelectionViewController.rootViewController.view layoutIfNeeded]; + } completion:^(BOOL finished) { + [aDateSelectionViewController willMoveToParentViewController:nil]; + [aDateSelectionViewController viewWillDisappear:YES]; + + [aDateSelectionViewController.view removeFromSuperview]; + [aDateSelectionViewController removeFromParentViewController]; + + [aDateSelectionViewController didMoveToParentViewController:nil]; + [aDateSelectionViewController viewDidDisappear:YES]; + + [aDateSelectionViewController.backgroundView removeFromSuperview]; + aDateSelectionViewController.window = nil; + aDateSelectionViewController.hasBeenDismissed = NO; + }]; +} + +#pragma mark - Init and Dealloc +- (id)init { + self = [super init]; + if(self) { + self.blurEffectStyle = UIBlurEffectStyleExtraLight; + + [self setupUIElements]; + } + return self; +} + +- (void)setupUIElements { + //Instantiate elements + self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + self.nowButton = [UIButton buttonWithType:UIButtonTypeSystem]; + self.datePicker = [[UIDatePicker alloc] initWithFrame:CGRectZero]; + + self.cancelAndSelectButtonSeperator = [[UIView alloc] initWithFrame:CGRectZero]; + self.cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; + self.selectButton = [UIButton buttonWithType:UIButtonTypeSystem]; + + //Setup properties of elements + self.titleLabel.backgroundColor = [UIColor clearColor]; + self.titleLabel.textColor = [UIColor grayColor]; + self.titleLabel.font = [UIFont systemFontOfSize:12]; + self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.titleLabel.textAlignment = NSTextAlignmentCenter; + self.titleLabel.numberOfLines = 0; + + [self.nowButton setTitle:[RMDateSelectionViewController localizedTitleForNowButton] forState:UIControlStateNormal]; + [self.nowButton addTarget:self action:@selector(nowButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + self.nowButton.titleLabel.font = [UIFont systemFontOfSize:[UIFont buttonFontSize]]; + self.nowButton.backgroundColor = [UIColor clearColor]; + self.nowButton.layer.cornerRadius = 4; + self.nowButton.clipsToBounds = YES; + self.nowButton.translatesAutoresizingMaskIntoConstraints = NO; + + self.datePicker.layer.cornerRadius = 4; + self.datePicker.translatesAutoresizingMaskIntoConstraints = NO; + + if ([RMDateSelectionViewController imageForSelectButton]) { + [self.cancelButton setImage:[RMDateSelectionViewController imageForCancelButton] forState:UIControlStateNormal]; + } else { + [self.cancelButton setTitle:[RMDateSelectionViewController localizedTitleForCancelButton] forState:UIControlStateNormal]; + } + + [self.cancelButton addTarget:self action:@selector(cancelButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + self.cancelButton.titleLabel.font = [UIFont systemFontOfSize:[UIFont buttonFontSize]]; + self.cancelButton.layer.cornerRadius = 4; + self.cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.cancelButton setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; + + if ([RMDateSelectionViewController imageForSelectButton]) { + [self.selectButton setImage:[RMDateSelectionViewController imageForSelectButton] forState:UIControlStateNormal]; + } else { + [self.selectButton setTitle:[RMDateSelectionViewController localizedTitleForSelectButton] forState:UIControlStateNormal]; + } + + [self.selectButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + self.selectButton.titleLabel.font = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]]; + self.selectButton.layer.cornerRadius = 4; + self.selectButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.selectButton setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; +} + +- (void)setupContainerElements { + if(!self.disableBlurEffects) { + UIBlurEffect *blur = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; + UIVibrancyEffect *vibrancy = [UIVibrancyEffect effectForBlurEffect:blur]; + + UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancy]; + vibrancyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + self.titleLabelContainer = [[UIVisualEffectView alloc] initWithEffect:blur]; + [((UIVisualEffectView *)self.titleLabelContainer).contentView addSubview:vibrancyView]; + } else { + self.titleLabelContainer = [[UIView alloc] initWithFrame:CGRectZero]; + } + + if(!self.disableBlurEffects) { + UIBlurEffect *blur = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; + UIVibrancyEffect *vibrancy = [UIVibrancyEffect effectForBlurEffect:blur]; + + UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancy]; + vibrancyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + self.nowButtonContainer = [[UIVisualEffectView alloc] initWithEffect:blur]; + [((UIVisualEffectView *)self.nowButtonContainer).contentView addSubview:vibrancyView]; + } else { + self.nowButtonContainer = [[UIView alloc] initWithFrame:CGRectZero]; + } + + if(!self.disableBlurEffects) { + UIBlurEffect *blur = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; + UIVibrancyEffect *vibrancy = [UIVibrancyEffect effectForBlurEffect:blur]; + + UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancy]; + vibrancyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + self.datePickerContainer = [[UIVisualEffectView alloc] initWithEffect:blur]; + [((UIVisualEffectView *)self.datePickerContainer).contentView addSubview:vibrancyView]; + } else { + self.datePickerContainer = [[UIView alloc] initWithFrame:CGRectZero]; + } + + if(!self.disableBlurEffects) { + UIBlurEffect *blur = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; + UIVibrancyEffect *vibrancy = [UIVibrancyEffect effectForBlurEffect:blur]; + + UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancy]; + vibrancyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + self.cancelAndSelectButtonContainer = [[UIVisualEffectView alloc] initWithEffect:blur]; + [((UIVisualEffectView *)self.cancelAndSelectButtonContainer).contentView addSubview:vibrancyView]; + } else { + self.cancelAndSelectButtonContainer = [[UIView alloc] initWithFrame:CGRectZero]; + } + + if(!self.disableBlurEffects) { + [[[[[(UIVisualEffectView *)self.titleLabelContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.titleLabel]; + [[[[[(UIVisualEffectView *)self.nowButtonContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.nowButton]; + [[[[[(UIVisualEffectView *)self.datePickerContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.datePicker]; + + [[[[[(UIVisualEffectView *)self.cancelAndSelectButtonContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.cancelAndSelectButtonSeperator]; + [[[[[(UIVisualEffectView *)self.cancelAndSelectButtonContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.cancelButton]; + [[[[[(UIVisualEffectView *)self.cancelAndSelectButtonContainer contentView] subviews] objectAtIndex:0] contentView] addSubview:self.selectButton]; + + self.titleLabelContainer.backgroundColor = [UIColor clearColor]; + self.nowButtonContainer.backgroundColor = [UIColor clearColor]; + self.datePickerContainer.backgroundColor = [UIColor clearColor]; + self.cancelAndSelectButtonContainer.backgroundColor = [UIColor clearColor]; + } else { + [self.titleLabelContainer addSubview:self.titleLabel]; + [self.nowButtonContainer addSubview:self.nowButton]; + [self.datePickerContainer addSubview:self.datePicker]; + + [self.cancelAndSelectButtonContainer addSubview:self.cancelAndSelectButtonSeperator]; + [self.cancelAndSelectButtonContainer addSubview:self.cancelButton]; + [self.cancelAndSelectButtonContainer addSubview:self.selectButton]; + + self.titleLabelContainer.backgroundColor = [UIColor whiteColor]; + self.nowButtonContainer.backgroundColor = [UIColor whiteColor]; + self.datePickerContainer.backgroundColor = [UIColor whiteColor]; + self.cancelAndSelectButtonContainer.backgroundColor = [UIColor whiteColor]; + } + + self.titleLabelContainer.layer.cornerRadius = 4; + self.titleLabelContainer.clipsToBounds = YES; + self.titleLabelContainer.translatesAutoresizingMaskIntoConstraints = NO; + + self.nowButtonContainer.layer.cornerRadius = 4; + self.nowButtonContainer.clipsToBounds = YES; + self.nowButtonContainer.translatesAutoresizingMaskIntoConstraints = NO; + + self.datePickerContainer.layer.cornerRadius = 4; + self.datePickerContainer.clipsToBounds = YES; + self.datePickerContainer.translatesAutoresizingMaskIntoConstraints = NO; + + self.cancelAndSelectButtonContainer.layer.cornerRadius = 4; + self.cancelAndSelectButtonContainer.clipsToBounds = YES; + self.cancelAndSelectButtonContainer.translatesAutoresizingMaskIntoConstraints = NO; + + self.cancelAndSelectButtonSeperator.backgroundColor = [UIColor lightGrayColor]; + self.cancelAndSelectButtonSeperator.translatesAutoresizingMaskIntoConstraints = NO; +} + +- (void)setupConstraints { + UIView *pickerContainer = self.datePickerContainer; + UIView *cancelSelectContainer = self.cancelAndSelectButtonContainer; + UIView *seperator = self.cancelAndSelectButtonSeperator; + UIButton *cancel = self.cancelButton; + UIButton *select = self.selectButton; + UIDatePicker *picker = self.datePicker; + UIView *labelContainer = self.titleLabelContainer; + UILabel *label = self.titleLabel; + UIButton *now = self.nowButton; + UIView *nowContainer = self.nowButtonContainer; + + NSDictionary *bindingsDict = NSDictionaryOfVariableBindings(cancelSelectContainer, seperator, pickerContainer, cancel, select, picker, labelContainer, label, now, nowContainer); + + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(10)-[pickerContainer]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(10)-[cancelSelectContainer]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[pickerContainer]-(10)-[cancelSelectContainer(44)]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + self.pickerHeightConstraint = [NSLayoutConstraint constraintWithItem:self.datePickerContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:RM_DATE_PICKER_HEIGHT_PORTRAIT]; + [self.view addConstraint:self.pickerHeightConstraint]; + + [self.datePickerContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[picker]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.cancelAndSelectButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[cancel]-(0)-[seperator(0.5)]-(0)-[select]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.cancelAndSelectButtonContainer addConstraint:[NSLayoutConstraint constraintWithItem:self.cancelAndSelectButtonSeperator attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.cancelAndSelectButtonContainer attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; + + [self.datePickerContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[picker]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.cancelAndSelectButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[cancel]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.cancelAndSelectButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[seperator]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.cancelAndSelectButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[select]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + + BOOL showTitle = self.titleLabel.text && self.titleLabel.text.length != 0; + BOOL showNowButton = !self.hideNowButton; + + if(showNowButton) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(10)-[nowContainer]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + + [self.nowButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[now]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + [self.nowButtonContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[now]-(0)-|" options:0 metrics:nil views:bindingsDict]]; + } + + if(showTitle) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(10)-[labelContainer]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + + [self.titleLabelContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(10)-[label]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + [self.titleLabelContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[label]-(10)-|" options:0 metrics:nil views:bindingsDict]]; + } + + NSDictionary *metricsDict = @{@"TopMargin": @(self.presentationType == RMDateSelectionViewControllerPresentationTypePopover ? 10 : 0)}; + + if(showNowButton && showTitle) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(TopMargin)-[labelContainer]-(10)-[now(44)]-(10)-[pickerContainer]" options:0 metrics:metricsDict views:bindingsDict]]; + } else if(showNowButton && !showTitle) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(TopMargin)-[nowContainer(44)]-(10)-[pickerContainer]" options:0 metrics:metricsDict views:bindingsDict]]; + } else if(!showNowButton && showTitle) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(TopMargin)-[labelContainer]-(10)-[pickerContainer]" options:0 metrics:metricsDict views:bindingsDict]]; + } else if(!showNowButton && !showTitle) { + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(TopMargin)-[pickerContainer]" options:0 metrics:metricsDict views:bindingsDict]]; + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.translatesAutoresizingMaskIntoConstraints = NO; + self.view.backgroundColor = [UIColor clearColor]; + self.view.layer.masksToBounds = YES; + + [self setupContainerElements]; + + if(self.titleLabel.text && self.titleLabel.text.length != 0) + [self.view addSubview:self.titleLabelContainer]; + + if(!self.hideNowButton) + [self.view addSubview:self.nowButtonContainer]; + + [self.view addSubview:self.datePickerContainer]; + [self.view addSubview:self.cancelAndSelectButtonContainer]; + + [self setupConstraints]; + + if(self.disableBlurEffects) { + if(self.tintColor) { + self.nowButton.tintColor = self.tintColor; + self.cancelButton.tintColor = self.tintColor; + self.selectButton.tintColor = self.tintColor; + } else { + self.nowButton.tintColor = [UIColor colorWithRed:0 green:122./255. blue:1 alpha:1]; + self.cancelButton.tintColor = [UIColor colorWithRed:0 green:122./255. blue:1 alpha:1]; + self.selectButton.tintColor = [UIColor colorWithRed:0 green:122./255. blue:1 alpha:1]; + } + } + + if(self.backgroundColor) { + if(!self.disableBlurEffects) { + [((UIVisualEffectView *)self.titleLabelContainer).contentView setBackgroundColor:self.backgroundColor]; + [((UIVisualEffectView *)self.nowButtonContainer).contentView setBackgroundColor:self.backgroundColor]; + [((UIVisualEffectView *)self.datePickerContainer).contentView setBackgroundColor:self.backgroundColor]; + [((UIVisualEffectView *)self.cancelAndSelectButtonContainer).contentView setBackgroundColor:self.backgroundColor]; + } else { + self.titleLabelContainer.backgroundColor = self.backgroundColor; + self.nowButtonContainer.backgroundColor = self.backgroundColor; + self.datePickerContainer.backgroundColor = self.backgroundColor; + self.cancelAndSelectButtonContainer.backgroundColor = self.backgroundColor; + } + } + + if(self.selectedBackgroundColor) { + if(!self.disableBlurEffects) { + [self.nowButton setBackgroundImage:[self imageWithColor:[self.selectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + [self.cancelButton setBackgroundImage:[self imageWithColor:[self.selectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + [self.selectButton setBackgroundImage:[self imageWithColor:[self.selectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + } else { + [self.nowButton setBackgroundImage:[self imageWithColor:self.selectedBackgroundColor] forState:UIControlStateHighlighted]; + [self.cancelButton setBackgroundImage:[self imageWithColor:self.selectedBackgroundColor] forState:UIControlStateHighlighted]; + [self.selectButton setBackgroundImage:[self imageWithColor:self.selectedBackgroundColor] forState:UIControlStateHighlighted]; + } + } + + if(!self.disableMotionEffects) + [self addMotionEffects]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRotate) name:UIDeviceOrientationDidChangeNotification object:nil]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + + [super viewDidDisappear:animated]; +} + +#pragma mark - Orientation +- (void)didRotate { + NSTimeInterval duration = 0.4; + + if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { + duration = 0.3; + + if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + self.pickerHeightConstraint.constant = RM_DATE_PICKER_HEIGHT_LANDSCAPE; + } else { + self.pickerHeightConstraint.constant = RM_DATE_PICKER_HEIGHT_PORTRAIT; + } + + [self.datePicker setNeedsUpdateConstraints]; + [self.datePicker layoutIfNeeded]; + } + + [self.rootViewController.view setNeedsUpdateConstraints]; + __weak RMDateSelectionViewController *blockself = self; + [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + [blockself.rootViewController.view layoutIfNeeded]; + } completion:^(BOOL finished) { + }]; +} + +#pragma mark - Helper +- (void)addMotionEffects { + [self.view addMotionEffect:self.motionEffectGroup]; +} + +- (void)removeMotionEffects { + [self.view removeMotionEffect:self.motionEffectGroup]; +} + +- (UIImage *)imageWithColor:(UIColor *)color { + CGRect rect = CGRectMake(0, 0, 1, 1); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0); + [color setFill]; + UIRectFill(rect); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + +#pragma mark - Properties +- (BOOL)disableBlurEffects { + if(!NSClassFromString(@"UIBlurEffect") || !NSClassFromString(@"UIVibrancyEffect") || !NSClassFromString(@"UIVisualEffectView")) { + return YES; + } else if(UIAccessibilityIsReduceTransparencyEnabled()) { + return YES; + } + + return _disableBlurEffects; +} + +- (BOOL)disableMotionEffects { + if(UIAccessibilityIsReduceMotionEnabled()) { + return YES; + } + + return _disableMotionEffects; +} + +- (void)setDisableMotionEffects:(BOOL)newDisableMotionEffects { + if(_disableMotionEffects != newDisableMotionEffects) { + _disableMotionEffects = newDisableMotionEffects; + + if([self isViewLoaded]) { + if(newDisableMotionEffects) { + [self removeMotionEffects]; + } else { + [self addMotionEffects]; + } + } + } +} + +- (BOOL)disableBouncingWhenShowing { + if(UIAccessibilityIsReduceMotionEnabled()) { + return YES; + } + + return _disableBouncingWhenShowing; +} + +- (UIMotionEffectGroup *)motionEffectGroup { + if(!_motionEffectGroup) { + UIInterpolatingMotionEffect *verticalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + verticalMotionEffect.minimumRelativeValue = @(-10); + verticalMotionEffect.maximumRelativeValue = @(10); + + UIInterpolatingMotionEffect *horizontalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + horizontalMotionEffect.minimumRelativeValue = @(-10); + horizontalMotionEffect.maximumRelativeValue = @(10); + + _motionEffectGroup = [UIMotionEffectGroup new]; + _motionEffectGroup.motionEffects = @[horizontalMotionEffect, verticalMotionEffect]; + } + + return _motionEffectGroup; +} + +- (UIWindow *)window { + if(!_window) { + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + _window.windowLevel = UIWindowLevelStatusBar; + + RMNonRotatingDateSelectionViewController *rootViewController = [[RMNonRotatingDateSelectionViewController alloc] init]; + rootViewController.preferredStatusBarStyle = self.preferredStatusBarStyle; + rootViewController.statusBarHiddenMode = self.statusBarHiddenMode; + _window.rootViewController = rootViewController; + } + + return _window; +} + +- (UIView *)backgroundView { + if(!_backgroundView) { + self.backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; + _backgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; + _backgroundView.translatesAutoresizingMaskIntoConstraints = NO; + + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundViewTapped:)]; + [_backgroundView addGestureRecognizer:tapRecognizer]; + } + + return _backgroundView; +} + +- (void)setTintColor:(UIColor *)newTintColor { + if(_tintColor != newTintColor) { + _tintColor = newTintColor; + + if(!self.disableBlurEffects) { + self.datePicker.tintColor = newTintColor; + } + + self.nowButton.tintColor = newTintColor; + self.cancelButton.tintColor = newTintColor; + self.selectButton.tintColor = newTintColor; + } +} + +- (void)setBackgroundColor:(UIColor *)newBackgroundColor { + if(_backgroundColor != newBackgroundColor) { + _backgroundColor = newBackgroundColor; + + if([self isViewLoaded]) { + if(!self.disableBlurEffects && + [self.titleLabelContainer isKindOfClass:[UIVisualEffectView class]] && + [self.nowButtonContainer isKindOfClass:[UIVisualEffectView class]] && + [self.datePickerContainer isKindOfClass:[UIVisualEffectView class]] && + [self.cancelAndSelectButtonContainer isKindOfClass:[UIVisualEffectView class]]) { + [((UIVisualEffectView *)self.titleLabelContainer).contentView setBackgroundColor:newBackgroundColor]; + [((UIVisualEffectView *)self.nowButtonContainer).contentView setBackgroundColor:newBackgroundColor]; + [((UIVisualEffectView *)self.datePickerContainer).contentView setBackgroundColor:newBackgroundColor]; + [((UIVisualEffectView *)self.cancelAndSelectButtonContainer).contentView setBackgroundColor:newBackgroundColor]; + } else { + self.titleLabelContainer.backgroundColor = newBackgroundColor; + self.nowButtonContainer.backgroundColor = newBackgroundColor; + self.datePickerContainer.backgroundColor = newBackgroundColor; + self.cancelAndSelectButtonContainer.backgroundColor = newBackgroundColor; + } + } + } +} + +- (UIColor *)selectedBackgroundColor { + if(!_selectedBackgroundColor) { + self.selectedBackgroundColor = [UIColor colorWithWhite:230./255. alpha:1]; + } + + return _selectedBackgroundColor; +} + +- (void)setSelectedBackgroundColor:(UIColor *)newSelectedBackgroundColor { + if(_selectedBackgroundColor != newSelectedBackgroundColor) { + _selectedBackgroundColor = newSelectedBackgroundColor; + + if(!self.disableBlurEffects) { + [self.nowButton setBackgroundImage:[self imageWithColor:[newSelectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + [self.cancelButton setBackgroundImage:[self imageWithColor:[newSelectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + [self.selectButton setBackgroundImage:[self imageWithColor:[newSelectedBackgroundColor colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; + } else { + [self.nowButton setBackgroundImage:[self imageWithColor:newSelectedBackgroundColor] forState:UIControlStateHighlighted]; + [self.cancelButton setBackgroundImage:[self imageWithColor:newSelectedBackgroundColor] forState:UIControlStateHighlighted]; + [self.selectButton setBackgroundImage:[self imageWithColor:newSelectedBackgroundColor] forState:UIControlStateHighlighted]; + } + } +} + +#pragma mark - Presenting +- (void)show { + [self showWithSelectionHandler:nil andCancelHandler:nil]; +} + +- (void)showWithSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock { + self.selectedDateBlock = selectionBlock; + self.cancelBlock = cancelBlock; + + self.presentationType = RMDateSelectionViewControllerPresentationTypeWindow; + self.rootViewController = self.window.rootViewController; + + [RMDateSelectionViewController showDateSelectionViewController:self animated:YES]; +} + +- (void)showFromViewController:(UIViewController *)aViewController { + [self showFromViewController:aViewController withSelectionHandler:nil andCancelHandler:nil]; +} + +- (void)showFromViewController:(UIViewController *)aViewController withSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock { + if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + if([aViewController isKindOfClass:[UITableViewController class]]) { + if(aViewController.navigationController) { + NSLog(@"Warning: -[RMDateSelectionViewController %@] has been called with an instance of UITableViewController as argument. Trying to use the navigation controller of the UITableViewController instance instead.", NSStringFromSelector(_cmd)); + aViewController = aViewController.navigationController; + } else { + NSLog(@"Error: -[RMDateSelectionViewController %@] has been called with an instance of UITableViewController as argument. Showing the date selection view controller from an instance of UITableViewController is not possible due to some internals of UIKit. To prevent your app from crashing, showing the date selection view controller will be canceled.", NSStringFromSelector(_cmd)); + return; + } + } + + self.selectedDateBlock = selectionBlock; + self.cancelBlock = cancelBlock; + + self.presentationType = RMDateSelectionViewControllerPresentationTypeViewController; + self.rootViewController = aViewController; + + [RMDateSelectionViewController showDateSelectionViewController:self animated:YES]; + } else { + NSLog(@"Warning: -[RMDateSelectionViewController %@] has been called on an iPhone. This method is iPad only so we will use -[RMDateSelectionViewController %@] instead.", NSStringFromSelector(_cmd), NSStringFromSelector(@selector(showWithSelectionHandler:andCancelHandler:))); + [self showWithSelectionHandler:selectionBlock andCancelHandler:cancelBlock]; + } +} + +- (void)showFromRect:(CGRect)aRect inView:(UIView *)aView { + [self showFromRect:aRect inView:aView withSelectionHandler:nil andCancelHandler:nil]; +} + +- (void)showFromRect:(CGRect)aRect inView:(UIView *)aView withSelectionHandler:(RMDateSelectionBlock)selectionBlock andCancelHandler:(RMDateCancelBlock)cancelBlock { + if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + self.selectedDateBlock = selectionBlock; + self.cancelBlock = cancelBlock; + + self.presentationType = RMDateSelectionViewControllerPresentationTypePopover; + CGSize fittingSize = [self.view systemLayoutSizeFittingSize:CGSizeMake(0, 0)]; + + self.popover = [[UIPopoverController alloc] initWithContentViewController:self]; + self.popover.delegate = self; + self.popover.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; + self.popover.popoverContentSize = CGSizeMake(fittingSize.width, fittingSize.height+10); + + [self.popover presentPopoverFromRect:aRect inView:aView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } else { + NSLog(@"Warning: -[RMDateSelectionViewController %@] has been called on an iPhone. This method is iPad only so we will use -[RMDateSelectionViewController %@] instead.", NSStringFromSelector(_cmd), NSStringFromSelector(@selector(showWithSelectionHandler:andCancelHandler:))); + [self showWithSelectionHandler:selectionBlock andCancelHandler:cancelBlock]; + } +} + +- (void)dismiss { + if(self.presentationType == RMDateSelectionViewControllerPresentationTypePopover && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + self.popover.delegate = nil; + + [self.popover dismissPopoverAnimated:YES]; + self.popover = nil; + } else { + [RMDateSelectionViewController dismissDateSelectionViewController:self]; + } +} + +#pragma mark - Actions +- (IBAction)doneButtonPressed:(id)sender { + if(!self.hasBeenDismissed) { + self.hasBeenDismissed = YES; + + [self.delegate dateSelectionViewController:self didSelectDate:self.datePicker.date]; + if (self.selectedDateBlock) { + self.selectedDateBlock(self, self.datePicker.date); + } + [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1]; + } +} + +- (IBAction)cancelButtonPressed:(id)sender { + if(!self.hasBeenDismissed) { + self.hasBeenDismissed = YES; + + if ([self.delegate respondsToSelector:@selector(dateSelectionViewControllerDidCancel:)]) { + [self.delegate dateSelectionViewControllerDidCancel:self]; + } + + if (self.cancelBlock) { + self.cancelBlock(self); + } + [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1]; + } +} + +- (IBAction)nowButtonPressed:(id)sender { + if([self.delegate respondsToSelector:@selector(dateSelectionViewControllerNowButtonPressed:)]) { + [self.delegate dateSelectionViewControllerNowButtonPressed:self]; + } else { + [self.datePicker setDate:[[NSDate date] dateByRoundingToMinutes:self.datePicker.minuteInterval]]; + } +} + +- (IBAction)backgroundViewTapped:(UIGestureRecognizer *)sender { + if(!self.backgroundTapsDisabled && !self.hasBeenDismissed) { + self.hasBeenDismissed = YES; + + if ([self.delegate respondsToSelector:@selector(dateSelectionViewControllerDidCancel:)]) { + [self.delegate dateSelectionViewControllerDidCancel:self]; + } + + if (self.cancelBlock) { + self.cancelBlock(self); + } + [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1]; + } +} + +#pragma mark - UIPopoverController Delegates +- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { + if(!self.hasBeenDismissed) { + self.hasBeenDismissed = YES; + + if ([self.delegate respondsToSelector:@selector(dateSelectionViewControllerDidCancel:)]) { + [self.delegate dateSelectionViewControllerDidCancel:self]; + } + if (self.cancelBlock) { + self.cancelBlock(self); + } + [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1]; + } + + self.popover = nil; +} + +@end diff --git a/Anyway/SwiftyUserDefaults.swift b/Anyway/SwiftyUserDefaults.swift new file mode 100755 index 0000000..cc330b8 --- /dev/null +++ b/Anyway/SwiftyUserDefaults.swift @@ -0,0 +1,159 @@ +// +// SwiftyUserDefaults +// +// Copyright (c) 2015 Radosław Pietruszewski +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +public extension NSUserDefaults { + class Proxy { + private let defaults: NSUserDefaults + private let key: String + + private init(_ defaults: NSUserDefaults, _ key: String) { + self.defaults = defaults + self.key = key + } + + // MARK: Getters + + public var object: NSObject? { + return defaults.objectForKey(key) as? NSObject + } + + public var string: String? { + return defaults.stringForKey(key) + } + + public var array: NSArray? { + return defaults.arrayForKey(key) + } + + public var dictionary: NSDictionary? { + return defaults.dictionaryForKey(key) + } + + public var data: NSData? { + return defaults.dataForKey(key) + } + + public var date: NSDate? { + return object as? NSDate + } + + public var number: NSNumber? { + return object as? NSNumber + } + + public var int: Int? { + return number?.integerValue + } + + public var double: Double? { + return number?.doubleValue + } + + public var bool: Bool? { + return number?.boolValue + } + } + + /// Returns getter proxy for `key` + + public subscript(key: String) -> Proxy { + return Proxy(self, key) + } + + /// Sets value for `key` + + public subscript(key: String) -> Any? { + get { + return self[key] + } + set { + if let v = newValue as? Int { + setInteger(v, forKey: key) + } else if let v = newValue as? Double { + setDouble(v, forKey: key) + } else if let v = newValue as? Bool { + setBool(v, forKey: key) + } else if let v = newValue as? NSObject { + setObject(v, forKey: key) + } else if newValue == nil { + removeObjectForKey(key) + } else { + assertionFailure("Invalid value type") + } + } + } + + /// Returns `true` if `key` exists + + public func hasKey(key: String) -> Bool { + return objectForKey(key) != nil + } + + /// Removes value for `key` + + public func remove(key: String) { + removeObjectForKey(key) + } +} + +infix operator ?= { + associativity right + precedence 90 +} + +/// If key doesn't exist, sets its value to `expr` +/// Note: This isn't the same as `Defaults.registerDefaults`. This method saves the new value to disk, whereas `registerDefaults` only modifies the defaults in memory. +/// Note: If key already exists, the expression after ?= isn't evaluated + +public func ?= (proxy: NSUserDefaults.Proxy, @autoclosure expr: () -> Any) { + if !proxy.defaults.hasKey(proxy.key) { + proxy.defaults[proxy.key] = expr() + } +} + +/// Adds `b` to the key (and saves it as an integer) +/// If key doesn't exist or isn't a number, sets value to `b` + +public func += (proxy: NSUserDefaults.Proxy, b: Int) { + let a = proxy.defaults[proxy.key].int ?? 0 + proxy.defaults[proxy.key] = a + b +} + +public func += (proxy: NSUserDefaults.Proxy, b: Double) { + let a = proxy.defaults[proxy.key].double ?? 0 + proxy.defaults[proxy.key] = a + b +} + +/// Icrements key by one (and saves it as an integer) +/// If key doesn't exist or isn't a number, sets value to 1 + +public postfix func ++ (proxy: NSUserDefaults.Proxy) { + proxy += 1 +} + +/// Global shortcut for NSUserDefaults.standardUserDefaults() + +public let Defaults = NSUserDefaults.standardUserDefaults() diff --git a/Anyway/Utilities.swift b/Anyway/Utilities.swift new file mode 100644 index 0000000..2026b33 --- /dev/null +++ b/Anyway/Utilities.swift @@ -0,0 +1,767 @@ +// +// Utilities.swift +// SpeakApp +// +// Created by Aviel Gross on 10/22/14. +// Copyright (c) 2014 Aviel Gross. All rights reserved. +// + +import UIKit + +// MARK: Operators/Typealiases + +/** +* Set lhs to be rhs only if lhs is nil +* Example: imageView.image ?= placeholderImage +* Will set placeholderImage only if imageView.image is nil +*/ +infix operator ?= {} +func ?=(inout lhs: T!, rhs: T) { + if lhs == nil { + lhs = rhs + } +} +func ?=(inout lhs: T?, rhs: T) { + if lhs == nil { + lhs = rhs + } +} +func ?=(inout lhs: T?, rhs: T?) { + if lhs == nil { + lhs = rhs + } +} + +typealias Seconds = NSTimeInterval + +//MARK: Tick/Tock +private var tickD = NSDate() +func TICK() { + tickD = NSDate() +} +func TOCK(sender: Any = __FUNCTION__) { + print("⏰ TICK/TOCK for: \(sender) :: \(-tickD.timeIntervalSinceNow) ⏰") +} + +// MARK: Common/Generic + +func local(key: String, comment: String = "") -> String { + return NSLocalizedString(key, comment: comment) +} + +var brString: String { return "_________________________________________________________________" } +func printbr() { + print(brString) +} +func printFunc(val: Any = __FUNCTION__) { + print("🚩 \(val)") +} + +func prettyPrint(val: T, filename: NSString = __FILE__, line: Int = __LINE__, funcname: String = __FUNCTION__) +{ + print("\(NSDate()) [\(filename.lastPathComponent):\(line)] - \(funcname):\r\(val)\n") +} + +public func resizeImage(var image : UIImage, size : CGSize) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, 0); + image.drawInRect(CGRectMake(0, 0, size.width, size.height)) + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +func delay(delay:Double, closure:()->()) { + dispatch_after( + dispatch_time( + DISPATCH_TIME_NOW, + Int64(delay * Double(NSEC_PER_SEC)) + ), + dispatch_get_main_queue(), closure) +} + +func async(back:()->(T), then main:(T)->()) { + let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT + dispatch_async(dispatch_get_global_queue(priority, 0)) { + let some = back() + dispatch_async(dispatch_get_main_queue()) { + main(some) + } + } +} + +func async(back:()->(), then main:()->()) { + let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT + dispatch_async(dispatch_get_global_queue(priority, 0)) { + back() + dispatch_async(dispatch_get_main_queue()) { + main() + } + } +} + +func sync(main:()->()) { + dispatch_async(dispatch_get_main_queue()) { + main() + } +} + +func async(back:()->()) { + let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT + dispatch_async(dispatch_get_global_queue(priority, 0)) { + back() + } +} + +extension NSData { + var unarchived: AnyObject? { return NSKeyedUnarchiver.unarchiveObjectWithData(self) } +} + +extension Array { + func safeRetrieveElement(index: Int) -> Element? { + if count > index { return self[index] } + return nil + } + + mutating func removeFirst(element: Element, equality: (Element, Element) -> Bool) -> Bool { + for (index, item) in enumerate() { + if equality(item, element) { + self.removeAtIndex(index) + return true + } + } + return false + } + + mutating func removeFirst(compareTo: (Element) -> Bool) -> Bool { + for (index, item) in enumerate() { + if compareTo(item) { + self.removeAtIndex(index) + return true + } + } + return false + } +} + +extension UIStoryboardSegue { + func destinationController(type: T.Type) -> T? { + if let destNav = destinationViewController as? UINavigationController, + let dest = destNav.topViewController as? T { + return dest + } + + if let dest = destinationViewController as? T { + return dest + } + + return nil + } +} + +extension UIViewController { + func parentViewController(ofType type:T.Type) -> T? { + if let parentVC = presentingViewController as? UINavigationController, + let topParent = parentVC.topViewController as? T { + return topParent + } else + if let parentVC = presentingViewController as? T { + return parentVC + } + return nil + } +} + +extension UIAlertController { + + func show() { + present(animated: true, completion: nil) + } + + func present(animated animated: Bool, completion: (() -> Void)?) { + if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController { + presentFromController(rootVC, animated: animated, completion: completion) + } + } + + private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) { + if let navVC = controller as? UINavigationController, + let visibleVC = navVC.visibleViewController { + presentFromController(visibleVC, animated: animated, completion: completion) + } else + if let tabVC = controller as? UITabBarController, + let selectedVC = tabVC.selectedViewController { + presentFromController(selectedVC, animated: animated, completion: completion) + } else { + controller.presentViewController(self, animated: animated, completion: completion) + } + } +} + +extension UIAlertView { + class func show(title: String?, message: String?, closeTitle: String?) { + UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: closeTitle).show() + } +} + +extension UIImageView { + func setImage(url: String, placeHolder: UIImage? = nil, animated: Bool = true) { + if let placeHolderImage = placeHolder { + self.image = placeHolderImage + } + + UIImage.image(url, image: { (image) in + if animated { + UIView.transitionWithView(self, duration: 0.25, options: .TransitionCrossDissolve, animations: { + self.image = image + }) { done in } + } else { + self.image = image + } + }) + } +} + +extension UIImage { + + func resizeToSquare() -> UIImage? { + let originalWidth = self.size.width + let originalHeight = self.size.height + + var edge: CGFloat + if originalWidth > originalHeight { + edge = originalHeight + } else { + edge = originalWidth + } + + let posX = (originalWidth - edge) / 2.0 + let posY = (originalHeight - edge) / 2.0 + + let cropSquare = CGRectMake(posX, posY, edge, edge) + + if let imageRef = CGImageCreateWithImageInRect(self.CGImage, cropSquare) + { + return UIImage(CGImage: imageRef, scale: UIScreen.mainScreen().scale, orientation: self.imageOrientation) + } + return nil + + } + + /// Returns an image in the given size (in pixels) + func resized(size: CGSize) -> UIImage? { + let image = self.CGImage + + let bitsPerComponent = CGImageGetBitsPerComponent(image) + let bytesPerRow = CGImageGetBytesPerRow(image) + let colorSpace = CGImageGetColorSpace(image) + let bitmapInfo = CGImageGetBitmapInfo(image) + + let context = CGBitmapContextCreate(nil, Int(size.width), Int(size.height), bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo.rawValue) + CGContextSetInterpolationQuality(context, CGInterpolationQuality.Medium) + CGContextDrawImage(context, CGRect(origin: CGPointZero, size: size), image) + + if let aCGImage = CGBitmapContextCreateImage(context) + { + return UIImage(CGImage: aCGImage) + } + + return nil + + } + + func scaled(scale: CGFloat) -> UIImage? { + return resized(CGSizeMake(self.size.width * scale, self.size.height * scale)) + } + + func resizedToFullHD() -> UIImage? { return resize(maxLongEdge: 1920) } + func resizedToMediumHD() -> UIImage? { return resize(maxLongEdge: 1080) } + func resizeToThumbnail() -> UIImage? { return resize(maxLongEdge: 50) } + + func resize(maxLongEdge maxLongEdge: CGFloat) -> UIImage? { + let longEdge = max(size.width, size.height) + let shortEdge = min(size.width, size.height) + + if longEdge <= maxLongEdge { + return self + } + + let scale = maxLongEdge/longEdge + if longEdge == size.width { + return resized(CGSize(width: maxLongEdge, height: shortEdge * scale)) + } else { + return resized(CGSize(width: shortEdge * scale, height: maxLongEdge)) + } + } + + class func image(link: String, session: NSURLSession = NSURLSession.sharedSession(), image: (UIImage)->()) { + let url = NSURL(string: link)! + let downloadPhotoTask = session.downloadTaskWithURL(url) { (location, response, err) in + if let location = location, + let data = NSData(contentsOfURL: location), + let img = UIImage(data: data) { + dispatch_async(dispatch_get_main_queue()) { + image(img) + } + } + } + downloadPhotoTask.resume() + } + + class func imageWithInitials(initials: String, diameter: CGFloat, textColor: UIColor = UIColor.darkGrayColor(), backColor: UIColor = UIColor.lightGrayColor(), font: UIFont = UIFont.systemFontOfSize(14)) -> UIImage { + let size = CGSizeMake(diameter, diameter) + let r = size.width / 2 + + UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale) + let context = UIGraphicsGetCurrentContext() + CGContextSetStrokeColorWithColor(context, backColor.CGColor) + CGContextSetFillColorWithColor(context, backColor.CGColor) + + let path = UIBezierPath(ovalInRect: CGRectMake(0, 0, size.width, size.height)) + path.addClip() + path.lineWidth = 1.0 + path.stroke() + + CGContextSetFillColorWithColor(context, backColor.CGColor) + CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height)) + + let dict = [NSFontAttributeName: font, NSForegroundColorAttributeName: textColor] + let nsInitials = initials as NSString + let textSize = nsInitials.sizeWithAttributes(dict) + nsInitials.drawInRect(CGRectMake(r - textSize.width / 2, r - font.lineHeight / 2, size.width, size.height), withAttributes: dict) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image + } + +} + +extension UITextField { + + func passRegex(expression: Regex) -> Bool { + if let text = self.text where text.empty + { + return false + } + return (self.text ?? "").passRegex(expression) + } +} + +extension UIActivityIndicatorView { + + /// Calls 'startAnimating()' or 'stopAnimating()'. Returns 'isAnimating()' + var animating: Bool { + set{ + if newValue { startAnimating() } + else { stopAnimating() } + } + get { + return isAnimating() + } + } +} + +extension UITableView { + func reloadData(completion: ()->()) { + UIView.animateWithDuration(0, animations: { self.reloadData() }) + { _ in completion() } + } + + func calculateHeightForConfiguredSizingCell(cell: UITableViewCell) -> CGFloat { + cell.bounds.size = CGSize(width: frame.width, height: cell.bounds.height) + cell.setNeedsLayout() + cell.layoutIfNeeded() + let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) + return size.height + 1 + } + + func deselectRowIfNeeded(animated animate: Bool = true) { + if let selected = indexPathForSelectedRow { + deselectRowAtIndexPath(selected, animated: animate) + } + } + + /** + !!WILL OVERRIDE ANY EXISTING TABLE FOOTER THAT MIGHT EXIST!! + */ + func hideEmptySeperators() { + tableFooterView = UIView(frame: CGRectZero) + } +} + + +enum Regex: String { + case FullName = ".*\\s.*." // two words with a space + case Email = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" + case Password = "^[\\d\\w]{6,255}$" + case LetterOrDigit = "[a-zA-Z0-9]" + case Digit = "[0-9]" +} + +extension String { + + func passRegex(expression: Regex) -> Bool { + var error: NSError? + let regex: NSRegularExpression? + do { + regex = try NSRegularExpression(pattern: expression.rawValue, options: NSRegularExpressionOptions.CaseInsensitive) + } catch let error1 as NSError { + error = error1 + regex = nil + } + + if error != nil { + print(error) + } + + if self.empty { + return false + } + + let str: NSString = self as NSString + let options: NSMatchingOptions = NSMatchingOptions() + let numOfMatches = regex!.numberOfMatchesInString(str as String, options: options, range: str.rangeOfString(str as String)) + + return numOfMatches > 0 + } + + func attributedStringFromHTMLString(overrideFont: Bool = false, color: UIColor = UIColor.whiteColor()) -> NSAttributedString { + let options = [ + NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding + ] + + var atStr: NSMutableAttributedString? + do { + + atStr = try NSMutableAttributedString(data: self.dataUsingEncoding(NSUTF8StringEncoding)!, options: options as! [String : AnyObject], documentAttributes: nil) + if let str = atStr + { + let range = NSMakeRange(0, NSString(string: str.string).length) + + str.addAttribute(NSForegroundColorAttributeName, value: color, range: range) + + if overrideFont { + str.addAttribute(NSFontAttributeName, value: UIFont.preferredFontForTextStyle(UIFontTextStyleBody), range:range) + } + + atStr = str + } + + } + catch let error as NSError + { + prettyPrint(error) + } + + if let s = atStr + { + return s + } + + return NSMutableAttributedString(string: "") + } + + func stringByTrimmingHTMLTags() -> String { + return self.stringByReplacingOccurrencesOfString("<[^>]+>", withString: "", options: .RegularExpressionSearch, range: nil) + } + + func firstWord() -> String? { + return self.componentsSeparatedByString(" ").first + } + + func lastWord() -> String? { + return self.componentsSeparatedByString(" ").last + } + + var firstChar: String? { return empty ? nil : String(self[0]) } + var lastChar: String? { return String(self[endIndex.predecessor()]) } + + func firstCharAsLetterOrDigit() -> String? { + if let f = firstChar where f.passRegex(.LetterOrDigit) { return f } + return nil + } + + var empty: Bool { + return self.characters.count == 0 + } + + /// Either empty or only whitespace and/or new lines + var emptyDeduced: Bool { + return empty || stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).empty + } + + // discussion: http://stackoverflow.com/a/2933145/2242359 + func stringByForcingWritingDirectionLTR() -> String { + return "\u{200E}".stringByAppendingString(self) + } + + func stringByForcingWritingDirectionRTL() -> String { + return "\u{200F}".stringByAppendingString(self) + } + /** + Returns the caller string with apostrophes. + e.g., + "Hello" will return "\"Hello\"" + */ + func forceApostrophes() -> String { + return "\"\(self)\"" + } + + func contains(substring: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool { + var options = NSStringCompareOptions() + if ignoreCase { options.insert(NSStringCompareOptions.CaseInsensitiveSearch) } + if ignoreDiacritic { options.insert(NSStringCompareOptions.DiacriticInsensitiveSearch) } + return rangeOfString(substring, options: options) != nil + } + + subscript (i: Int) -> Character { + return self[self.startIndex.advancedBy(i)] + } + + subscript (i: Int) -> String { + return String(self[i] as Character) + } + + subscript (r: Range) -> String { + return substringWithRange(Range(start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex))) + } + +} + +extension NSMutableAttributedString { + public func addAttribute(name: String, value: AnyObject, ranges: [NSRange]) { + for r in ranges { + addAttribute(name, value: value, range: r) + } + } +} + + +extension Int { + var ordinal: String { + get { + var suffix: String = "" + let ones: Int = self % 10; + let tens: Int = (self/10) % 10; + + if (tens == 1) { + suffix = "th"; + } else if (ones == 1){ + suffix = "st"; + } else if (ones == 2){ + suffix = "nd"; + } else if (ones == 3){ + suffix = "rd"; + } else { + suffix = "th"; + } + + return suffix + } + } + + var string: String { + get{ + return "\(self)" + } + } +} + +extension UIView { + @IBInspectable var cornerRadius: CGFloat { + get { + return layer.cornerRadius + } + set { + layer.cornerRadius = newValue + layer.masksToBounds = newValue > 0 + } + } + + func shakeView() { + let shake:CABasicAnimation = CABasicAnimation(keyPath: "position") + shake.duration = 0.1 + shake.repeatCount = 2 + shake.autoreverses = true + + let from_point:CGPoint = CGPointMake(self.center.x - 5, self.center.y) + let from_value:NSValue = NSValue(CGPoint: from_point) + + let to_point:CGPoint = CGPointMake(self.center.x + 5, self.center.y) + let to_value:NSValue = NSValue(CGPoint: to_point) + + shake.fromValue = from_value + shake.toValue = to_value + self.layer.addAnimation(shake, forKey: "position") + } + + func blowView() { + UIView.animateWithDuration(0.1, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -> Void in + self.transform = CGAffineTransformMakeScale(1.06, 1.05) + }) { _ in + + UIView.animateWithDuration(0.06, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in + self.transform = CGAffineTransformIdentity + }) { _ in } + + } + } + + func snapShotImage(afterScreenUpdates after: Bool) -> UIImage? { + UIGraphicsBeginImageContext(self.bounds.size) + self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: after) + let snap = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return snap + } +} + +extension NSNotificationCenter { + + class func post(name: String) { + defaultCenter().postNotificationName(name, object: nil) + } + + class func observe(name: String, usingBlock block: (NSNotification!) -> Void) -> NSObjectProtocol { + return defaultCenter().addObserverForName(name, object: nil, queue: nil, usingBlock:block) + } + + class func observe(target: T, name: String, usingBlock block: (note: NSNotification!, targetRef: T) -> Void) -> NSObjectProtocol { + weak var weakTarget = target + return defaultCenter().addObserverForName(name, object: nil, queue: nil) { + if let strTarget = weakTarget { + block(note: $0, targetRef: strTarget) + } + } + } + +} + +extension NSDate { + class func todayComponents() -> (day: Int, month: Int, year: Int) { + let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)! + let units: NSCalendarUnit = [NSCalendarUnit.Day, NSCalendarUnit.Month, NSCalendarUnit.Year] + let comps = calendar.components(units, fromDate: NSDate()) + return (comps.day, comps.month, comps.year) + } + + var shortDescription: String { return self.customDescription(.ShortStyle, date: .ShortStyle) } + var shortTime: String { return self.customDescription(.ShortStyle) } + var shortDate: String { return self.customDescription(date: .ShortStyle) } + var mediumDescription: String { return self.customDescription(.MediumStyle, date: .MediumStyle) } + var mediumTime: String { return self.customDescription(.MediumStyle) } + var mediumDate: String { return self.customDescription(date: .MediumStyle) } + var longDescription: String { return self.customDescription(.LongStyle, date: .LongStyle) } + var longTime: String { return self.customDescription(.LongStyle) } + var longDate: String { return self.customDescription(date: .LongStyle) } + + func customDescription(time: NSDateFormatterStyle = .NoStyle, date: NSDateFormatterStyle = .NoStyle) -> String { + let form = NSDateFormatter() + form.timeStyle = time + form.dateStyle = date + return form.stringFromDate(self) + } + + func formattedFromCompenents(styleAttitude: NSDateFormatterStyle, year: Bool = true, month: Bool = true, day: Bool = true, hour: Bool = true, minute: Bool = true, second: Bool = true) -> String { + let long = styleAttitude == .LongStyle || styleAttitude == .FullStyle ? true : false + var comps = "" + + if year { comps += long ? "yyyy" : "yy" } + if month { comps += long ? "MMMM" : "MMM" } + if day { comps += long ? "dd" : "d" } + + if hour { comps += long ? "HH" : "H" } + if minute { comps += long ? "mm" : "m" } + if second { comps += long ? "ss" : "s" } + + let format = NSDateFormatter.dateFormatFromTemplate(comps, options: 0, locale: NSLocale.currentLocale()) + let formatter = NSDateFormatter() + formatter.dateFormat = format + return formatter.stringFromDate(self) + } + + func formatted(format: String) -> String { + let form = NSDateFormatter() + form.dateFormat = format + return form.stringFromDate(self) + } + + /** + Init an NSDate with string and format. eg. (W3C format: "YYYY-MM-DDThh:mm:ss") + + - parameter val: the value with the date info + - parameter format: the format of the value string + - parameter timeZone: optional, default is GMT time (not current locale!) + + - returns: returns the created date, if succeeded + */ + convenience init?(val: String, format: String, timeZone: String = "") { + let form = NSDateFormatter() + form.dateFormat = format + if timeZone.emptyDeduced { + form.timeZone = NSTimeZone(forSecondsFromGMT: 0) + } else { + form.timeZone = NSTimeZone(name: timeZone) + } + if let date = form.dateFromString(val) { + self.init(timeIntervalSince1970: date.timeIntervalSince1970) + } else { + self.init() + return nil + } + } +} + +extension UIApplication { + + static var isAppInForeground: Bool { + return sharedApplication().applicationState == UIApplicationState.Active + } + + func registerForPushNotifications() { + + let types: UIUserNotificationType = ([.Alert, .Badge, .Sound]) + let settings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil) + + registerUserNotificationSettings(settings) + registerForRemoteNotifications() + } + +} + +extension NSFileManager +{ + class func documentsDir() -> String + { + var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) + return paths[0] + } + + class func cachesDir() -> String + { + var paths = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true) + return paths[0] + } + + class func tempBaseDir() -> String! + { + print("temp - \(NSTemporaryDirectory())") + return NSTemporaryDirectory() + } + +} + +func + (left: Dictionary, right: Dictionary) -> Dictionary { + var map = Dictionary() + for (k, v) in left { + map[k] = v + } + for (k, v) in right { + map[k] = v + } + return map +} + + diff --git a/Anyway/ViewController.swift b/Anyway/ViewController.swift new file mode 100644 index 0000000..637f557 --- /dev/null +++ b/Anyway/ViewController.swift @@ -0,0 +1,516 @@ +// +// ViewController.swift +// Anyway +// +// Created by Aviel Gross on 2/16/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit +import MapKit + +private func newHud() -> JGProgressHUD { + let hud = JGProgressHUD(style: .Light) + hud.animation = JGProgressHUDFadeZoomAnimation() as JGProgressHUDFadeZoomAnimation + hud.interactionType = JGProgressHUDInteractionType.BlockNoTouches + return hud +} + +public class Filter { + var startDate = NSDate(timeIntervalSince1970: 1356991200) { didSet{ valueChanged() } } // Jan 1st 2013 + var endDate = NSDate(timeIntervalSince1970: 1388527200) { didSet{ valueChanged() } } // Jan 1st 2014 + var showFatal = true { didSet{ valueChanged() } } + var showSevere = true { didSet{ valueChanged() } } + var showLight = true { didSet{ valueChanged() } } + var showInaccurate = false { didSet{ valueChanged() } } + + var description: String { return "FILTER: Fatal: \(showFatal) | Severe: \(showSevere) | Light: \(showLight) | Inaccurate: \(showInaccurate)" } + + var onChange: ()->() = {} + func valueChanged() { print("filter changed"); onChange() } +} + +class ViewController: UIViewController, MKMapViewDelegate, UITableViewDelegate, UITableViewDataSource, RMDateSelectionViewControllerDelegate, CLLocationManagerDelegate { + + + + enum DateSelectionType { case None, Start, End } + + @IBOutlet weak var btnFilter: UIBarButtonItem! + @IBOutlet weak var btnAccidents: UIBarButtonItem! + @IBOutlet weak var btnInfo: UIButton! + + + @IBOutlet weak var detailLabel: UILabel! { + didSet{ + detailLabel?.backgroundColor = UIColor.whiteColor() + detailLabel?.layer.borderColor = UIColor.grayColor().CGColor + detailLabel?.layer.borderWidth = 0.5 + detailLabel?.layer.cornerRadius = 4 + detailLabel?.layer.masksToBounds = true + } + } + @IBOutlet weak var map: OCMapView! + + @IBOutlet weak var backBlackView: UIView! + @IBOutlet weak var tableViewContainer: UIView! + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var constraintTableViewBottom: NSLayoutConstraint! + @IBOutlet weak var constraintTableViewHeight: NSLayoutConstraint! + + var filter = Filter() + var dateSelectionType = DateSelectionType.None + + var lastRegion = MKCoordinateRegionForMapRect(MKMapRectNull) + let network = Network() + let hud = newHud() + var gettingInfo = false + + var initialLayout = true + + var shouldJumpToStartLocation = true + + + //MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + map.delegate = self + map.clusterSize = 0.1 + map.minimumAnnotationCountPerCluster = 4 + + filter.onChange = { self.updateInfoIfPossible(self.map, filterChanged:true) } + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + backBlackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "closeTableView")) + } + + override func viewDidAppear(animated: Bool) { + super.viewDidAppear(animated) + + if !isLocationMonitoringAuthorized() { + sync{ self.beginTrackingLocation() } + } + } + + override func viewWillLayoutSubviews() { + if initialLayout { + initialLayout = false + + let rows = CGFloat(totalRowsForTable(.Filter)) + let rowHeight = CGFloat(44)// tableView.rowHeight -> is -1 at this point + let sections = CGFloat(numberOfSectionsInTableView(tableView)) + let headerHeight = CGFloat( tableView(tableView, heightForHeaderInSection: 0) ) + + constraintTableViewHeight.constant = rowHeight * rows + headerHeight * sections + constraintTableViewBottom.constant = -constraintTableViewHeight.constant + } + } + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + if segue.identifier == DetailViewController.segueIdentifier { + guard let + dest = segue.destinationViewController as? DetailViewController, + markerView = sender as? MarkerView, + marker = markerView.annotation as? Marker + else {return} + + dest.detailData = marker + } + else if segue.identifier == AccidentsViewController.segueId { + if + let nav = segue.destinationViewController as? UINavigationController, + dest = nav.viewControllers.first as? AccidentsViewController + { + + // get map annotations as MarkerAnnotation + let annots = map.annotations.flatMap{ ($0 as? MarkerAnnotation) ?? nil } + + // break any MarkerGroup and create Marker array + var markers = [Marker]() + for annot in annots { + if let group = annot as? MarkerGroup { + markers += group.markers + } + if let marker = annot as? Marker { + markers.append(marker) + } + } + + + dest.dataSource = markers.sort{$0.created.compare($1.created) == .OrderedDescending} + } + } + } + + + //MARK: - Logic + + var isMapCloseEnoughToFetchData: Bool { + return btnFilter.enabled + } + + func setAreaCanFetchDataUI(canFetch: Bool) { + if !canFetch { + self.detailLabel.text = "איזור גדול מדי, נסה להתקרב" + self.detailLabel.hidden = false + self.btnAccidents.title = "תאונות" + } + btnFilter.enabled = canFetch + btnAccidents.enabled = canFetch + } + + func updateInfoIfPossible(map: MKMapView, filterChanged: Bool) { + + //If too far - don't get anything + if Int(map.edgesDistance()) > MAX_DIST_OF_MAP_EDGES { + self.setAreaCanFetchDataUI(false) + return + } + self.setAreaCanFetchDataUI(true) + + //If zommed in - don't update + if !filterChanged && MKCoordinateRegionContainsRegion(lastRegion, map.region) && map.visibleAnnotations().count > 0 { + return + } + + + //if gettingInfo { return } + gettingInfo = true + //self.detailLabel.text = "..." + hud.showInView(view) + + print("Getting some...") + network.getAnnotations(map.edgePoints(), filter: filter) { marks, count in + print("finished parsing") + self.map.annotationsToIgnore = nil + self.map.removeAnnotations(self.map.annotations) + self.map.addAnnotations(marks) +// self.detailLabel.text = "מציג \(count) תאונות" + self.detailLabel.hidden = true + self.btnAccidents.title = "מציג \(count) תאונות" + self.gettingInfo = false + self.hud.dismiss() + } + + } + + //MARK: - Actions + @IBAction func actionFilter(sender: UIBarButtonItem) { + openTableView(.Filter) + } + + @IBAction func actionAccidents(sender: UIBarButtonItem) { + + } + + + enum TableViewType { case Closed, Filter, Accidents } + + var tableViewType = TableViewType.Closed + + func openTableView(type: TableViewType) { + tableViewType = type +// backBlackView.alpha = 0 + backBlackView.hidden = false + constraintTableViewBottom.constant = 0 + view.bringSubviewToFront(tableViewContainer) + UIView.animateWithDuration(0.25) { + self.tableViewContainer.layoutIfNeeded() + self.backBlackView.alpha = 1 + } + } + + func closeTableView() { + tableViewType = .Closed + + constraintTableViewBottom.constant = -constraintTableViewHeight.constant + UIView.animateWithDuration(0.25, animations:{ + self.tableViewContainer.layoutIfNeeded() + self.backBlackView.alpha = 0 + }) { _ in + self.backBlackView.hidden = true + } + } + + func openDateSelectionController() { + let dateSelectionVC = RMDateSelectionViewController.dateSelectionController() + dateSelectionVC.datePicker.datePickerMode = UIDatePickerMode.Date + dateSelectionVC.disableBouncingWhenShowing = true + dateSelectionVC.delegate = self + dateSelectionVC.show() + } + + //MARK: - Table View + + enum TableType { + case Filter, Accidents + } + + func numberOfRowsForTable(type: TableType, _ section: Int) -> Int { + switch type { + case .Filter: return section == 0 ? 2 : 4 + case .Accidents: return 0 + } + } + func totalRowsForTable(type: TableType) -> Int { + switch type { + case .Filter: return 6 + case .Accidents: return 0 + } + } + + func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return 2; + } + + func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 22; + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return numberOfRowsForTable(.Filter, section) + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let dateId = "dateFilterCellIdentifier" + let switchId = "switchFilterCellIdentifier" + var cell: FilterCellTableViewCell! + + switch (indexPath.row, indexPath.section) { +// case (0, 0): fallthrough //Pick start date + case (_, 0): //Pick end date + cell = tableView.dequeueReusableCellWithIdentifier(dateId) as! FilterCellTableViewCell + cell.selectionStyle = UITableViewCellSelectionStyle.Default + default: + cell = tableView.dequeueReusableCellWithIdentifier(switchId) as! FilterCellTableViewCell + cell.selectionStyle = UITableViewCellSelectionStyle.None + } + + switch (indexPath.row, indexPath.section) { + case (0, 0): cell.filterType = .StartDate + case (1, 0): cell.filterType = .EndDate + case (0, 1): cell.filterType = .ShowFatal + case (1, 1): cell.filterType = .ShowSevere + case (2, 1): cell.filterType = .ShowLight + case (3, 1): cell.filterType = .ShowInaccurate + default: break + } + + cell.filter = filter + + return cell + } + + func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + switch (indexPath.row, indexPath.section) { + case (0, 0): //Pick start date + dateSelectionType = .Start + closeTableView() + openDateSelectionController() + case (1, 0): //Pick end date + dateSelectionType = .End + closeTableView() + openDateSelectionController() + default: + break + } + + self.tableView.deselectRowAtIndexPath(indexPath, animated: false) + } + + //MARK: - Scrollview + + func scrollViewDidScroll(scrollView: UIScrollView) { + if scrollView == tableView { + constraintTableViewBottom.constant += scrollView.contentOffset.y + constraintTableViewBottom.constant = min(0, constraintTableViewBottom.constant) + scrollView.contentOffset = CGPointZero + tableView.setNeedsLayout() + } + } + + func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + if scrollView == tableView { + let delta = constraintTableViewHeight.constant / 3 + if abs(constraintTableViewBottom.constant) > delta || velocity.y < -1 { + closeTableView() + } else { + openTableView(tableViewType) + } + } + } + + //MARK: - MapView + + func mapViewRegionDidChangeFromUserInteraction() -> Bool { + if let view = map.subviews.first { + for gestureObj in view.gestureRecognizers ?? [] { + if let gesture = gestureObj as? UIGestureRecognizer { + if gesture.state == .Began || gesture.state == .Ended { + return true + } + } + } + } + return false + } + + func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + map.clusteringEnabled = Int(mapView.edgesDistance()) > MIN_DIST_CLUSTER_DISABLE + + if CLLocation.distance(from: lastRegion.center, to: mapView.region.center) > 50 { + updateInfoIfPossible(mapView, filterChanged:false) + } + lastRegion = mapView.region + } + + func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + network.cancelRequestIfNeeded() + + if hud.visible { + hud.dismissAnimated(false) + } + + if mapViewRegionDidChangeFromUserInteraction() { + shouldJumpToStartLocation = false + } + } + + func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView! { + if let cluster = annotation as? OCAnnotation { + let pin = ClusterView(annotation: cluster, reuseIdentifier: clusterReuseIdentifierDefault) + pin.label?.text = "\(cluster.annotationsInCluster().count)" + return pin + } + if let marker = annotation as? Marker { + + if let mView = mapView.dequeueReusableAnnotationViewWithIdentifier(markerReuseIdentifierDefault) as? MarkerView { + mView.annotation = marker + return mView + } + return MarkerView(marker: marker) + + } + if let markerGroup = annotation as? MarkerGroup { + + if let mView = mapView.dequeueReusableAnnotationViewWithIdentifier(markerGroupReuseIdentifierDefault) as? MarkerGroupView { + mView.annotation = markerGroup + return mView + } + return MarkerGroupView(markerGroup: markerGroup) + + } + return nil + } + + func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { + if let markerView = view as? MarkerView { + performSegueWithIdentifier(DetailViewController.segueIdentifier, sender: markerView) + } + } + + func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) { + if let groupView = view as? MarkerGroupView { + + if let ann = view.annotation { + mapView.removeAnnotation(ann) + } + + let markerGroup = groupView.annotation as! MarkerGroup + let markers = markerGroup.markers + if map.annotationsToIgnore == nil { + map.annotationsToIgnore = NSMutableSet() + } + map.annotationsToIgnore.addObjectsFromArray(markers) + +// for marker in markers { //to make sure... +// marker.coordinate = markerGroup.coordinate +// } + + let addMarkers = { + mapView.addAnnotations(markers) + } + + UIView.animateWithDuration(0, animations:addMarkers) { _ in + UIView.animateWithDuration(0.25, animations: { + AnnotationCoordinateUtility.repositionAnnotations(markers, toAvoidClashAtCoordination: markerGroup.coordinate, circleDistanceDelta: mapView.edgesDistance()/100) + }) + } + + } + } + + func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) { + if shouldJumpToStartLocation { + let user = map.userLocation + let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) + let mapRegion = MKCoordinateRegion(center: user.coordinate, span: span) + map.setRegion(mapRegion, animated: true) + } + } + + func mapView(mapView: MKMapView, didFailToLocateUserWithError error: NSError) { + if shouldJumpToStartLocation { + let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) + let mapRegion = MKCoordinateRegion(center: fallbackStartLocationCoordinate, span: span) + map.setRegion(mapRegion, animated: true) + } + } + + + //MARK: - Location Services + + func isLocationMonitoringAuthorized() -> Bool { + let status = CLLocationManager.authorizationStatus() + return status == .AuthorizedAlways || status == .AuthorizedWhenInUse + } + + let locationManager = CLLocationManager() + + func beginTrackingLocation() { + locationManager.delegate = self +// man.delegate = self + let status = CLLocationManager.authorizationStatus() + if status == CLAuthorizationStatus.NotDetermined { + //NEVER ASKED + if locationManager.respondsToSelector("requestWhenInUseAuthorization") { + locationManager.requestWhenInUseAuthorization() + } else { + locationManager.startUpdatingLocation() + } + } + else if status == .AuthorizedAlways || status == .AuthorizedWhenInUse { + //GRANTED + map.showsUserLocation = true + + } else if status == .Restricted { + //RESTRICTED + //TODO: Hide "show me" button + + } else if status == .Denied { + //DENIED + //TODO: Set a "denied" alert for when tapping "show me" button + } + } + + //MARK: - Date selection delegate + func dateSelectionViewController(vc: RMDateSelectionViewController!, didSelectDate aDate: NSDate!) { + if dateSelectionType == .Start { + filter.startDate = aDate + } else { + filter.endDate = aDate + } + tableView?.reloadData() + openTableView(.Filter) + } + func dateSelectionViewControllerDidCancel(vc: RMDateSelectionViewController!) { + openTableView(.Filter) + } + + +} + diff --git a/AnywayTests/AnywayTests.swift b/AnywayTests/AnywayTests.swift new file mode 100644 index 0000000..f065775 --- /dev/null +++ b/AnywayTests/AnywayTests.swift @@ -0,0 +1,36 @@ +// +// AnywayTests.swift +// AnywayTests +// +// Created by Aviel Gross on 2/16/15. +// Copyright (c) 2015 Hasadna. All rights reserved. +// + +import UIKit +import XCTest + +class AnywayTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + XCTAssert(true, "Pass") + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock() { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/AnywayTests/Info.plist b/AnywayTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/AnywayTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..b5e2882 --- /dev/null +++ b/Podfile @@ -0,0 +1,14 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '8.0' +# Uncomment this line if you're using Swift +use_frameworks! + +target 'Anyway' do + pod 'Alamofire', '~> 3.0' + pod 'SwiftyJSON' +end + +target 'AnywayTests' do + +end + diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..8f9fc0c --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,13 @@ +PODS: + - Alamofire (3.1.2) + - SwiftyJSON (2.3.1) + +DEPENDENCIES: + - Alamofire (~> 3.0) + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 7c16ca65f3c7e681fd925fd7f2dec7747ff96855 + SwiftyJSON: 592b53bee5ef3dd9b3bebc6b9cb7ee35426ae8c3 + +COCOAPODS: 0.39.0 diff --git a/Pods/Alamofire/LICENSE b/Pods/Alamofire/LICENSE new file mode 100644 index 0000000..5b7934d --- /dev/null +++ b/Pods/Alamofire/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Pods/Alamofire/README.md b/Pods/Alamofire/README.md new file mode 100644 index 0000000..cbeb5d9 --- /dev/null +++ b/Pods/Alamofire/README.md @@ -0,0 +1,1147 @@ +![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/assets/alamofire.png) + +[![Build Status](https://travis-ci.org/Alamofire/Alamofire.svg)](https://travis-ci.org/Alamofire/Alamofire) +[![Cocoapods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg)](https://img.shields.io/cocoapods/v/Alamofire.svg) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Platform](https://img.shields.io/cocoapods/p/Alamofire.svg?style=flat)](http://cocoadocs.org/docsets/Alamofire) +[![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat)](http://twitter.com/AlamofireSF) + +Alamofire is an HTTP networking library written in Swift. + +## Features + +- [x] Chainable Request / Response methods +- [x] URL / JSON / plist Parameter Encoding +- [x] Upload File / Data / Stream / MultipartFormData +- [x] Download using Request or Resume data +- [x] Authentication with NSURLCredential +- [x] HTTP Response Validation +- [x] TLS Certificate and Public Key Pinning +- [x] Progress Closure & NSProgress +- [x] cURL Debug Output +- [x] Comprehensive Unit Test Coverage +- [x] [Complete Documentation](http://cocoadocs.org/docsets/Alamofire) + +## Requirements + +- iOS 8.0+ / Mac OS X 10.9+ / tvOS 9.0+ / watchOS 2.0+ +- Xcode 7.1+ + +## Migration Guides + +- [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md) +- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) + +## Communication + +- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/alamofire). (Tag 'alamofire') +- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/alamofire). +- If you **found a bug**, open an issue. +- If you **have a feature request**, open an issue. +- If you **want to contribute**, submit a pull request. + +## Installation + +> **Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks (10.9).** +> +> Alamofire is no longer supported on iOS 7 due to the lack of support for frameworks. Without frameworks, running Travis-CI against iOS 7 would require a second duplicated test target. The separate test suite would need to import all the Swift files and the tests would need to be duplicated and re-written. This split would be too difficult to maintain to ensure the highest possible quality of the Alamofire ecosystem. + +### CocoaPods + +[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: + +```bash +$ gem install cocoapods +``` + +> CocoaPods 0.39.0+ is required to build Alamofire 3.0.0+. + +To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`: + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +use_frameworks! + +pod 'Alamofire', '~> 3.0' +``` + +Then, run the following command: + +```bash +$ pod install +``` + +### Carthage + +[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. + +You can install Carthage with [Homebrew](http://brew.sh/) using the following command: + +```bash +$ brew update +$ brew install carthage +``` + +To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: + +```ogdl +github "Alamofire/Alamofire" ~> 3.0 +``` + +Run `carthage` to build the framework and drag the built `Alamofire.framework` into your Xcode project. + +### Manually + +If you prefer not to use either of the aforementioned dependency managers, you can integrate Alamofire into your project manually. + +#### Embedded Framework + +- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: + +```bash +$ git init +``` + +- Add Alamofire as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command: + +```bash +$ git submodule add https://github.com/Alamofire/Alamofire.git +``` + +- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. + + > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. + +- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. +- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. +- In the tab bar at the top of that window, open the "General" panel. +- Click on the `+` button under the "Embedded Binaries" section. +- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. + + > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. + +- Select the top `Alamofire.framework` for iOS and the bottom one for OS X. + + > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as either `Alamofire iOS` or `Alamofire OSX`. + +- And that's it! + +> The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. + +--- + +## Usage + +### Making a Request + +```swift +import Alamofire + +Alamofire.request(.GET, "http://httpbin.org/get") +``` + +### Response Handling + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + .responseJSON { response in + print(response.request) // original URL request + print(response.response) // URL response + print(response.data) // server data + print(response.result) // result of response serialization + + if let JSON = response.result.value { + print("JSON: \(JSON)") + } + } +``` + +> Networking in Alamofire is done _asynchronously_. Asynchronous programming may be a source of frustration to programmers unfamiliar with the concept, but there are [very good reasons](https://developer.apple.com/library/ios/qa/qa1693/_index.html) for doing it this way. + +> Rather than blocking execution to wait for a response from the server, a [callback](http://en.wikipedia.org/wiki/Callback_%28computer_programming%29) is specified to handle the response once it's received. The result of a request is only available inside the scope of a response handler. Any execution contingent on the response or data received from the server must be done within a handler. + +### Response Serialization + +**Built-in Response Methods** + +- `response()` +- `responseData()` +- `responseString(encoding: NSStringEncoding)` +- `responseJSON(options: NSJSONReadingOptions)` +- `responsePropertyList(options: NSPropertyListReadOptions)` + +#### Response Handler + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + .response { request, response, data, error in + print(request) + print(response) + print(data) + print(error) + } +``` + +> The `response` serializer does NOT evaluate any of the response data. It merely forwards on all the information directly from the URL session delegate. We strongly encourage you to leverage the other responser serializers taking advantage of `Response` and `Result` types. + +#### Response Data Handler + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + .responseData { response in + print(response.request) + print(response.response) + print(response.result) + } +``` + +#### Response String Handler + +```swift +Alamofire.request(.GET, "http://httpbin.org/get") + .responseString { response in + print("Success: \(response.result.isSuccess)") + print("Response String: \(response.result.value)") + } +``` + +#### Response JSON Handler + +```swift +Alamofire.request(.GET, "http://httpbin.org/get") + .responseJSON { response in + debugPrint(response) + } +``` + +#### Chained Response Handlers + +Response handlers can even be chained: + +```swift +Alamofire.request(.GET, "http://httpbin.org/get") + .responseString { response in + print("Response String: \(response.result.value)") + } + .responseJSON { response in + print("Response JSON: \(response.result.value)") + } +``` + +### HTTP Methods + +`Alamofire.Method` lists the HTTP methods defined in [RFC 7231 §4.3](http://tools.ietf.org/html/rfc7231#section-4.3): + +```swift +public enum Method: String { + case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT +} +``` + +These values can be passed as the first argument of the `Alamofire.request` method: + +```swift +Alamofire.request(.POST, "http://httpbin.org/post") + +Alamofire.request(.PUT, "http://httpbin.org/put") + +Alamofire.request(.DELETE, "http://httpbin.org/delete") +``` + +### Parameters + +#### GET Request With URL-Encoded Parameters + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) +// http://httpbin.org/get?foo=bar +``` + +#### POST Request With URL-Encoded Parameters + +```swift +let parameters = [ + "foo": "bar", + "baz": ["a", 1], + "qux": [ + "x": 1, + "y": 2, + "z": 3 + ] +] + +Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters) +// HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3 +``` + +### Parameter Encoding + +Parameters can also be encoded as JSON, Property List, or any custom format, using the `ParameterEncoding` enum: + +```swift +enum ParameterEncoding { + case URL + case URLEncodedInURL + case JSON + case PropertyList(format: NSPropertyListFormat, options: NSPropertyListWriteOptions) + case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?)) + + func encode(request: NSURLRequest, parameters: [String: AnyObject]?) -> (NSURLRequest, NSError?) + { ... } +} +``` + +- `URL`: A query string to be set as or appended to any existing URL query for `GET`, `HEAD`, and `DELETE` requests, or set as the body for requests with any other HTTP method. The `Content-Type` HTTP header field of an encoded request with HTTP body is set to `application/x-www-form-urlencoded`. _Since there is no published specification for how to encode collection types, Alamofire follows the convention of appending `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for nested dictionary values (`foo[bar]=baz`)._ +- `URLEncodedInURL`: Creates query string to be set as or appended to any existing URL query. Uses the same implementation as the `.URL` case, but always applies the encoded result to the URL. +- `JSON`: Uses `NSJSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. +- `PropertyList`: Uses `NSPropertyListSerialization` to create a plist representation of the parameters object, according to the associated format and write options values, which is set as the body of the request. The `Content-Type` HTTP header field of an encoded request is set to `application/x-plist`. +- `Custom`: Uses the associated closure value to construct a new request given an existing request and parameters. + +#### Manual Parameter Encoding of an NSURLRequest + +```swift +let URL = NSURL(string: "http://httpbin.org/get")! +var request = NSMutableURLRequest(URL: URL) + +let parameters = ["foo": "bar"] +let encoding = Alamofire.ParameterEncoding.URL +(request, _) = encoding.encode(request, parameters: parameters) +``` + +#### POST Request with JSON-encoded Parameters + +```swift +let parameters = [ + "foo": [1,2,3], + "bar": [ + "baz": "qux" + ] +] + +Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters, encoding: .JSON) +// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}} +``` + +### HTTP Headers + +Adding a custom HTTP header to a `Request` is supported directly in the global `request` method. This makes it easy to attach HTTP headers to a `Request` that can be constantly changing. + +> For HTTP headers that do not change, it is recommended to set them on the `NSURLSessionConfiguration` so they are automatically applied to any `NSURLSessionTask` created by the underlying `NSURLSession`. + +```swift +let headers = [ + "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Content-Type": "application/x-www-form-urlencoded" +] + +Alamofire.request(.GET, "http://httpbin.org/get", headers: headers) + .responseJSON { response in + debugPrint(response) + } +``` + +### Caching + +Caching is handled on the system framework level by [`NSURLCache`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/cl/NSURLCache). + +### Uploading + +**Supported Upload Types** + +- File +- Data +- Stream +- MultipartFormData + +#### Uploading a File + +```swift +let fileURL = NSBundle.mainBundle().URLForResource("Default", withExtension: "png") +Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL) +``` + +#### Uploading with Progress + +```swift +Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL) + .progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in + print(totalBytesWritten) + + // This closure is NOT called on the main queue for performance + // reasons. To update your ui, dispatch to the main queue. + dispatch_async(dispatch_get_main_queue()) { + print("Total bytes written on main queue: \(totalBytesWritten)") + } + } + .responseJSON { response in + debugPrint(response) + } +``` + +#### Uploading MultipartFormData + +```swift +Alamofire.upload( + .POST, + "http://httpbin.org/post", + multipartFormData: { multipartFormData in + multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn") + multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow") + }, + encodingCompletion: { encodingResult in + switch encodingResult { + case .Success(let upload, _, _): + upload.responseJSON { response in + debugPrint(response) + } + case .Failure(let encodingError): + print(encodingError) + } + } +) +``` + +### Downloading + +**Supported Download Types** + +- Request +- Resume Data + +#### Downloading a File + +```swift +Alamofire.download(.GET, "http://httpbin.org/stream/100") { temporaryURL, response in + let fileManager = NSFileManager.defaultManager() + let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] + let pathComponent = response.suggestedFilename + + return directoryURL.URLByAppendingPathComponent(pathComponent!) +} +``` + +#### Using the Default Download Destination + +```swift +let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask) +Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination) +``` + +#### Downloading a File w/Progress + +```swift +Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination) + .progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in + print(totalBytesRead) + + // This closure is NOT called on the main queue for performance + // reasons. To update your ui, dispatch to the main queue. + dispatch_async(dispatch_get_main_queue()) { + print("Total bytes read on main queue: \(totalBytesRead)") + } + } + .response { _, _, _, error in + if let error = error { + print("Failed with error: \(error)") + } else { + print("Downloaded file successfully") + } + } +``` + +#### Accessing Resume Data for Failed Downloads + +```swift +Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination) + .response { _, _, data, _ in + if let + data = data, + resumeDataString = NSString(data: data, encoding: NSUTF8StringEncoding) + { + print("Resume Data: \(resumeDataString)") + } else { + print("Resume Data was empty") + } + } +``` + +> The `data` parameter is automatically populated with the `resumeData` if available. + +```swift +let download = Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination) +download.response { _, _, _, _ in + if let + resumeData = download.resumeData, + resumeDataString = NSString(data: resumeData, encoding: NSUTF8StringEncoding) + { + print("Resume Data: \(resumeDataString)") + } else { + print("Resume Data was empty") + } +} +``` + +### Authentication + +Authentication is handled on the system framework level by [`NSURLCredential` and `NSURLAuthenticationChallenge`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLAuthenticationChallenge_Class/Reference/Reference.html). + +**Supported Authentication Schemes** + +- [HTTP Basic](http://en.wikipedia.org/wiki/Basic_access_authentication) +- [HTTP Digest](http://en.wikipedia.org/wiki/Digest_access_authentication) +- [Kerberos](http://en.wikipedia.org/wiki/Kerberos_%28protocol%29) +- [NTLM](http://en.wikipedia.org/wiki/NT_LAN_Manager) + +#### HTTP Basic Authentication + +The `authenticate` method on a `Request` will automatically provide an `NSURLCredential` to an `NSURLAuthenticationChallenge` when appropriate: + +```swift +let user = "user" +let password = "password" + +Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)") + .authenticate(user: user, password: password) + .responseJSON { response in + debugPrint(response) + } +``` + +Depending upon your server implementation, an `Authorization` header may also be appropriate: + +```swift +let user = "user" +let password = "password" + +let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)! +let base64Credentials = credentialData.base64EncodedStringWithOptions([]) + +let headers = ["Authorization": "Basic \(base64Credentials)"] + +Alamofire.request(.GET, "http://httpbin.org/basic-auth/user/password", headers: headers) + .responseJSON { response in + debugPrint(response) + } +``` + +#### Authentication with NSURLCredential + +```swift +let user = "user" +let password = "password" + +let credential = NSURLCredential(user: user, password: password, persistence: .ForSession) + +Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)") + .authenticate(usingCredential: credential) + .responseJSON { response in + debugPrint(response) + } +``` + +### Validation + +By default, Alamofire treats any completed request to be successful, regardless of the content of the response. Calling `validate` before a response handler causes an error to be generated if the response had an unacceptable status code or MIME type. + +#### Manual Validation + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + .validate(statusCode: 200..<300) + .validate(contentType: ["application/json"]) + .response { response in + print(response) + } +``` + +#### Automatic Validation + +Automatically validates status code within `200...299` range, and that the `Content-Type` header of the response matches the `Accept` header of the request, if one is provided. + +```swift +Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + .validate() + .responseJSON { response in + switch response.result { + case .Success: + print("Validation Successful") + case .Failure(let error): + print(error) + } + } +``` + +### Printable + +```swift +let request = Alamofire.request(.GET, "http://httpbin.org/ip") + +print(request) +// GET http://httpbin.org/ip (200) +``` + +### DebugPrintable + +```swift +let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) + +debugPrint(request) +``` + +#### Output (cURL) + +```bash +$ curl -i \ + -H "User-Agent: Alamofire" \ + -H "Accept-Encoding: Accept-Encoding: gzip;q=1.0,compress;q=0.5" \ + -H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \ + "http://httpbin.org/get?foo=bar" +``` + +--- + +## Advanced Usage + +> Alamofire is built on `NSURLSession` and the Foundation URL Loading System. To make the most of +this framework, it is recommended that you be familiar with the concepts and capabilities of the underlying networking stack. + +**Recommended Reading** + +- [URL Loading System Programming Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html) +- [NSURLSession Class Reference](https://developer.apple.com/library/mac/documentation/Foundation/Reference/NSURLSession_class/Introduction/Introduction.html#//apple_ref/occ/cl/NSURLSession) +- [NSURLCache Class Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/cl/NSURLCache) +- [NSURLAuthenticationChallenge Class Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLAuthenticationChallenge_Class/Reference/Reference.html) + +### Manager + +Top-level convenience methods like `Alamofire.request` use a shared instance of `Alamofire.Manager`, which is configured with the default `NSURLSessionConfiguration`. + +As such, the following two statements are equivalent: + +```swift +Alamofire.request(.GET, "http://httpbin.org/get") +``` + +```swift +let manager = Alamofire.Manager.sharedInstance +manager.request(NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")!)) +``` + +Applications can create managers for background and ephemeral sessions, as well as new managers that customize the default session configuration, such as for default headers (`HTTPAdditionalHeaders`) or timeout interval (`timeoutIntervalForRequest`). + +#### Creating a Manager with Default Configuration + +```swift +let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() +let manager = Alamofire.Manager(configuration: configuration) +``` + +#### Creating a Manager with Background Configuration + +```swift +let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.example.app.background") +let manager = Alamofire.Manager(configuration: configuration) +``` + +#### Creating a Manager with Ephemeral Configuration + +```swift +let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration() +let manager = Alamofire.Manager(configuration: configuration) +``` + +#### Modifying Session Configuration + +```swift +var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:] +defaultHeaders["DNT"] = "1 (Do Not Track Enabled)" + +let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() +configuration.HTTPAdditionalHeaders = defaultHeaders + +let manager = Alamofire.Manager(configuration: configuration) +``` + +> This is **not** recommended for `Authorization` or `Content-Type` headers. Instead, use `URLRequestConvertible` and `ParameterEncoding`, respectively. + +### Request + +The result of a `request`, `upload`, or `download` method is an instance of `Alamofire.Request`. A request is always created using a constructor method from an owning manager, and never initialized directly. + +Methods like `authenticate`, `validate` and `responseData` return the caller in order to facilitate chaining. + +Requests can be suspended, resumed, and cancelled: + +- `suspend()`: Suspends the underlying task and dispatch queue +- `resume()`: Resumes the underlying task and dispatch queue. If the owning manager does not have `startRequestsImmediately` set to `true`, the request must call `resume()` in order to start. +- `cancel()`: Cancels the underlying task, producing an error that is passed to any registered response handlers. + +### Response Serialization + +#### Creating a Custom Response Serializer + +Alamofire provides built-in response serialization for strings, JSON, and property lists, but others can be added in extensions on `Alamofire.Request`. + +For example, here's how a response handler using [Ono](https://github.com/mattt/Ono) might be implemented: + +```swift +extension Request { + public static func XMLResponseSerializer() -> ResponseSerializer { + return ResponseSerializer { request, response, data, error in + guard error == nil else { return .Failure(error!) } + + guard let validData = data else { + let failureReason = "Data could not be serialized. Input data was nil." + let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + + do { + let XML = try ONOXMLDocument(data: validData) + return .Success(XML) + } catch { + return .Failure(error as NSError) + } + } + } + + public func responseXMLDocument(completionHandler: Response -> Void) -> Self { + return response(responseSerializer: Request.XMLResponseSerializer(), completionHandler: completionHandler) + } +} +``` + +#### Generic Response Object Serialization + +Generics can be used to provide automatic, type-safe response object serialization. + +```swift +public protocol ResponseObjectSerializable { + init?(response: NSHTTPURLResponse, representation: AnyObject) +} + +extension Request { + public func responseObject(completionHandler: Response -> Void) -> Self { + let responseSerializer = ResponseSerializer { request, response, data, error in + guard error == nil else { return .Failure(error!) } + + let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) + let result = JSONResponseSerializer.serializeResponse(request, response, data, error) + + switch result { + case .Success(let value): + if let + response = response, + responseObject = T(response: response, representation: value) + { + return .Success(responseObject) + } else { + let failureReason = "JSON could not be serialized into response object: \(value)" + let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + case .Failure(let error): + return .Failure(error) + } + } + + return response(responseSerializer: responseSerializer, completionHandler: completionHandler) + } +} +``` + +```swift +final class User: ResponseObjectSerializable { + let username: String + let name: String + + init?(response: NSHTTPURLResponse, representation: AnyObject) { + self.username = response.URL!.lastPathComponent! + self.name = representation.valueForKeyPath("name") as! String + } +} +``` + +```swift +Alamofire.request(.GET, "https://example.com/users/mattt") + .responseObject { (response: Response) in + debugPrint(response) + } +``` + +The same approach can also be used to handle endpoints that return a representation of a collection of objects: + +```swift +public protocol ResponseCollectionSerializable { + static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self] +} + +extension Alamofire.Request { + public func responseCollection(completionHandler: Response<[T], NSError> -> Void) -> Self { + let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in + guard error == nil else { return .Failure(error!) } + + let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments) + let result = JSONSerializer.serializeResponse(request, response, data, error) + + switch result { + case .Success(let value): + if let response = response { + return .Success(T.collection(response: response, representation: value)) + } else { + let failureReason = "Response collection could not be serialized due to nil response" + let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + case .Failure(let error): + return .Failure(error) + } + } + + return response(responseSerializer: responseSerializer, completionHandler: completionHandler) + } +} +``` + +```swift +final class User: ResponseObjectSerializable, ResponseCollectionSerializable { + let username: String + let name: String + + init?(response: NSHTTPURLResponse, representation: AnyObject) { + self.username = response.URL!.lastPathComponent! + self.name = representation.valueForKeyPath("name") as! String + } + + static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] { + var users: [User] = [] + + if let representation = representation as? [[String: AnyObject]] { + for userRepresentation in representation { + if let user = User(response: response, representation: userRepresentation) { + users.append(user) + } + } + } + + return users + } +} +``` + +```swift +Alamofire.request(.GET, "http://example.com/users") + .responseCollection { (response: Response<[User], NSError>) in + debugPrint(response) + } +``` + +### URLStringConvertible + +Types adopting the `URLStringConvertible` protocol can be used to construct URL strings, which are then used to construct URL requests. `NSString`, `NSURL`, `NSURLComponents`, and `NSURLRequest` conform to `URLStringConvertible` by default, allowing any of them to be passed as `URLString` parameters to the `request`, `upload`, and `download` methods: + +```swift +let string = NSString(string: "http://httpbin.org/post") +Alamofire.request(.POST, string) + +let URL = NSURL(string: string)! +Alamofire.request(.POST, URL) + +let URLRequest = NSURLRequest(URL: URL) +Alamofire.request(.POST, URLRequest) // overrides `HTTPMethod` of `URLRequest` + +let URLComponents = NSURLComponents(URL: URL, resolvingAgainstBaseURL: true) +Alamofire.request(.POST, URLComponents) +``` + +Applications interacting with web applications in a significant manner are encouraged to have custom types conform to `URLStringConvertible` as a convenient way to map domain-specific models to server resources. + +#### Type-Safe Routing + +```swift +extension User: URLStringConvertible { + static let baseURLString = "http://example.com" + + var URLString: String { + return User.baseURLString + "/users/\(username)/" + } +} +``` + +```swift +let user = User(username: "mattt") +Alamofire.request(.GET, user) // http://example.com/users/mattt +``` + +### URLRequestConvertible + +Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests. `NSURLRequest` conforms to `URLRequestConvertible` by default, allowing it to be passed into `request`, `upload`, and `download` methods directly (this is the recommended way to specify custom HTTP body for individual requests): + +```swift +let URL = NSURL(string: "http://httpbin.org/post")! +let mutableURLRequest = NSMutableURLRequest(URL: URL) +mutableURLRequest.HTTPMethod = "POST" + +let parameters = ["foo": "bar"] + +do { + mutableURLRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions()) +} catch { + // No-op +} + +mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + +Alamofire.request(mutableURLRequest) +``` + +Applications interacting with web applications in a significant manner are encouraged to have custom types conform to `URLRequestConvertible` as a way to ensure consistency of requested endpoints. Such an approach can be used to abstract away server-side inconsistencies and provide type-safe routing, as well as manage authentication credentials and other state. + +#### API Parameter Abstraction + +```swift +enum Router: URLRequestConvertible { + static let baseURLString = "http://example.com" + static let perPage = 50 + + case Search(query: String, page: Int) + + // MARK: URLRequestConvertible + + var URLRequest: NSMutableURLRequest { + let result: (path: String, parameters: [String: AnyObject]) = { + switch self { + case .Search(let query, let page) where page > 1: + return ("/search", ["q": query, "offset": Router.perPage * page]) + case .Search(let query, _): + return ("/search", ["q": query]) + } + }() + + let URL = NSURL(string: Router.baseURLString)! + let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(result.path)) + let encoding = Alamofire.ParameterEncoding.URL + + return encoding.encode(URLRequest, parameters: result.parameters).0 + } +} +``` + +```swift +Alamofire.request(Router.Search(query: "foo bar", page: 1)) // ?q=foo%20bar&offset=50 +``` + +#### CRUD & Authorization + +```swift +enum Router: URLRequestConvertible { + static let baseURLString = "http://example.com" + static var OAuthToken: String? + + case CreateUser([String: AnyObject]) + case ReadUser(String) + case UpdateUser(String, [String: AnyObject]) + case DestroyUser(String) + + var method: Alamofire.Method { + switch self { + case .CreateUser: + return .POST + case .ReadUser: + return .GET + case .UpdateUser: + return .PUT + case .DestroyUser: + return .DELETE + } + } + + var path: String { + switch self { + case .CreateUser: + return "/users" + case .ReadUser(let username): + return "/users/\(username)" + case .UpdateUser(let username, _): + return "/users/\(username)" + case .DestroyUser(let username): + return "/users/\(username)" + } + } + + // MARK: URLRequestConvertible + + var URLRequest: NSMutableURLRequest { + let URL = NSURL(string: Router.baseURLString)! + let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) + mutableURLRequest.HTTPMethod = method.rawValue + + if let token = Router.OAuthToken { + mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } + + switch self { + case .CreateUser(let parameters): + return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0 + case .UpdateUser(_, let parameters): + return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 + default: + return mutableURLRequest + } + } +} +``` + +```swift +Alamofire.request(Router.ReadUser("mattt")) // GET /users/mattt +``` + +### Security + +Using a secure HTTPS connection when communicating with servers and web services is an important step in securing sensitive data. By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework. While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities. In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the `ServerTrustPolicy`. + +#### ServerTrustPolicy + +The `ServerTrustPolicy` enumeration evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when connecting to a server over a secure HTTPS connection. + +```swift +let serverTrustPolicy = ServerTrustPolicy.PinCertificates( + certificates: ServerTrustPolicy.certificatesInBundle(), + validateCertificateChain: true, + validateHost: true +) +``` + +There are many different cases of server trust evaluation giving you complete control over the validation process: + +* `PerformDefaultEvaluation`: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge. +* `PinCertificates`: Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates. +* `PinPublicKeys`: Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys. +* `DisableEvaluation`: Disables all evaluation which in turn will always consider any server trust as valid. +* `CustomEvaluation`: Uses the associated closure to evaluate the validity of the server trust thus giving you complete control over the validation process. Use with caution. + +#### Server Trust Policy Manager + +The `ServerTrustPolicyManager` is responsible for storing an internal mapping of server trust policies to a particular host. This allows Alamofire to evaluate each host against a different server trust policy. + +```swift +let serverTrustPolicies: [String: ServerTrustPolicy] = [ + "test.example.com": .PinCertificates( + certificates: ServerTrustPolicy.certificatesInBundle(), + validateCertificateChain: true, + validateHost: true + ), + "insecure.expired-apis.com": .DisableEvaluation +] + +let manager = Manager( + serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies) +) +``` + +> Make sure to keep a reference to the new `Manager` instance, otherwise your requests will all get cancelled when your `manager` is deallocated. + +These server trust policies will result in the following behavior: + +* `test.example.com` will always use certificate pinning with certificate chain and host validation enabled thus requiring the following criteria to be met to allow the TLS handshake to succeed: + * Certificate chain MUST be valid. + * Certificate chain MUST include one of the pinned certificates. + * Challenge host MUST match the host in the certificate chain's leaf certificate. +* `insecure.expired-apis.com` will never evaluate the certificate chain and will always allow the TLS handshake to succeed. +* All other hosts will use the default evaluation provided by Apple. + +##### Subclassing Server Trust Policy Manager + +If you find yourself needing more flexible server trust policy matching behavior (i.e. wildcarded domains), then subclass the `ServerTrustPolicyManager` and override the `serverTrustPolicyForHost` method with your own custom implementation. + +```swift +class CustomServerTrustPolicyManager: ServerTrustPolicyManager { + override func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? { + var policy: ServerTrustPolicy? + + // Implement your custom domain matching behavior... + + return policy + } +} +``` + +#### Validating the Host + +The `.PerformDefaultEvaluation`, `.PinCertificates` and `.PinPublicKeys` server trust policies all take a `validateHost` parameter. Setting the value to `true` will cause the server trust evaluation to verify that hostname in the certificate matches the hostname of the challenge. If they do not match, evaluation will fail. A `validateHost` value of `false` will still evaluate the full certificate chain, but will not validate the hostname of the leaf certificate. + +> It is recommended that `validateHost` always be set to `true` in production environments. + +#### Validating the Certificate Chain + +Pinning certificates and public keys both have the option of validating the certificate chain using the `validateCertificateChain` parameter. By setting this value to `true`, the full certificate chain will be evaluated in addition to performing a byte equality check against the pinned certficates or public keys. A value of `false` will skip the certificate chain validation, but will still perform the byte equality check. + +There are several cases where it may make sense to disable certificate chain validation. The most common use cases for disabling validation are self-signed and expired certificates. The evaluation would always fail in both of these cases, but the byte equality check will still ensure you are receiving the certificate you expect from the server. + +> It is recommended that `validateCertificateChain` always be set to `true` in production environments. + +#### App Transport Security + +With the addition of App Transport Security (ATS) in iOS 9, it is possible that using a custom `ServerTrustPolicyManager` with several `ServerTrustPolicy` objects will have no effect. If you continuously see `CFNetwork SSLHandshake failed (-9806)` errors, you have probably run into this problem. Apple's ATS system overrides the entire challenge system unless you configure the ATS settings in your app's plist to disable enough of it to allow your app to evaluate the server trust. + +If you run into this problem (high probability with self-signed certificates), you can work around this issue by adding the following to your `Info.plist`. + +```xml + + NSAppTransportSecurity + + NSExceptionDomains + + example.com + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionRequiresForwardSecrecy + + NSIncludesSubdomains + + + + + +``` + +Whether you need to set the `NSExceptionRequiresForwardSecrecy` to `NO` depends on whether your TLS connection is using an allowed cipher suite. In certain cases, it will need to be set to `NO`. The `NSExceptionAllowsInsecureHTTPLoads` MUST be set to `YES` in order to allow the `SessionDelegate` to receive challenge callbacks. Once the challenge callbacks are being called, the `ServerTrustPolicyManager` will take over the server trust evaluation. + +> It is recommended to always use valid certificates in production environments. + +--- + +## Component Libraries + +In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. + +* [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache and a priority-based image downloading system. + +## Open Rdars + +The following rdars have some affect on the current implementation of Alamofire. + +* [rdar://22024442](http://www.openradar.me/radar?id=6082025006039040) - Array of [SecCertificate] crashing Swift 2.0 compiler in optimized builds +* [rdar://21349340](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in test case + +## FAQ + +### What's the origin of the name Alamofire? + +Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas. + +--- + +## Credits + +Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases. + +### Security Disclosure + +If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker. + +## License + +Alamofire is released under the MIT license. See LICENSE for details. diff --git a/Pods/Alamofire/Source/Alamofire.swift b/Pods/Alamofire/Source/Alamofire.swift new file mode 100644 index 0000000..3b52d0f --- /dev/null +++ b/Pods/Alamofire/Source/Alamofire.swift @@ -0,0 +1,368 @@ +// Alamofire.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +// MARK: - URLStringConvertible + +/** + Types adopting the `URLStringConvertible` protocol can be used to construct URL strings, which are then used to + construct URL requests. +*/ +public protocol URLStringConvertible { + /** + A URL that conforms to RFC 2396. + + Methods accepting a `URLStringConvertible` type parameter parse it according to RFCs 1738 and 1808. + + See https://tools.ietf.org/html/rfc2396 + See https://tools.ietf.org/html/rfc1738 + See https://tools.ietf.org/html/rfc1808 + */ + var URLString: String { get } +} + +extension String: URLStringConvertible { + public var URLString: String { + return self + } +} + +extension NSURL: URLStringConvertible { + public var URLString: String { + return absoluteString + } +} + +extension NSURLComponents: URLStringConvertible { + public var URLString: String { + return URL!.URLString + } +} + +extension NSURLRequest: URLStringConvertible { + public var URLString: String { + return URL!.URLString + } +} + +// MARK: - URLRequestConvertible + +/** + Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests. +*/ +public protocol URLRequestConvertible { + /// The URL request. + var URLRequest: NSMutableURLRequest { get } +} + +extension NSURLRequest: URLRequestConvertible { + public var URLRequest: NSMutableURLRequest { + return self.mutableCopy() as! NSMutableURLRequest + } +} + +// MARK: - Convenience + +func URLRequest( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil) + -> NSMutableURLRequest +{ + let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: URLString.URLString)!) + mutableURLRequest.HTTPMethod = method.rawValue + + if let headers = headers { + for (headerField, headerValue) in headers { + mutableURLRequest.setValue(headerValue, forHTTPHeaderField: headerField) + } + } + + return mutableURLRequest +} + +// MARK: - Request Methods + +/** + Creates a request using the shared manager instance for the specified method, URL string, parameters, and + parameter encoding. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter parameters: The parameters. `nil` by default. + - parameter encoding: The parameter encoding. `.URL` by default. + - parameter headers: The HTTP headers. `nil` by default. + + - returns: The created request. +*/ +public func request( + method: Method, + _ URLString: URLStringConvertible, + parameters: [String: AnyObject]? = nil, + encoding: ParameterEncoding = .URL, + headers: [String: String]? = nil) + -> Request +{ + return Manager.sharedInstance.request( + method, + URLString, + parameters: parameters, + encoding: encoding, + headers: headers + ) +} + +/** + Creates a request using the shared manager instance for the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request + + - returns: The created request. +*/ +public func request(URLRequest: URLRequestConvertible) -> Request { + return Manager.sharedInstance.request(URLRequest.URLRequest) +} + +// MARK: - Upload Methods + +// MARK: File + +/** + Creates an upload request using the shared manager instance for the specified method, URL string, and file. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter file: The file to upload. + + - returns: The created upload request. +*/ +public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + file: NSURL) + -> Request +{ + return Manager.sharedInstance.upload(method, URLString, headers: headers, file: file) +} + +/** + Creates an upload request using the shared manager instance for the specified URL request and file. + + - parameter URLRequest: The URL request. + - parameter file: The file to upload. + + - returns: The created upload request. +*/ +public func upload(URLRequest: URLRequestConvertible, file: NSURL) -> Request { + return Manager.sharedInstance.upload(URLRequest, file: file) +} + +// MARK: Data + +/** + Creates an upload request using the shared manager instance for the specified method, URL string, and data. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter data: The data to upload. + + - returns: The created upload request. +*/ +public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + data: NSData) + -> Request +{ + return Manager.sharedInstance.upload(method, URLString, headers: headers, data: data) +} + +/** + Creates an upload request using the shared manager instance for the specified URL request and data. + + - parameter URLRequest: The URL request. + - parameter data: The data to upload. + + - returns: The created upload request. +*/ +public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request { + return Manager.sharedInstance.upload(URLRequest, data: data) +} + +// MARK: Stream + +/** + Creates an upload request using the shared manager instance for the specified method, URL string, and stream. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter stream: The stream to upload. + + - returns: The created upload request. +*/ +public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + stream: NSInputStream) + -> Request +{ + return Manager.sharedInstance.upload(method, URLString, headers: headers, stream: stream) +} + +/** + Creates an upload request using the shared manager instance for the specified URL request and stream. + + - parameter URLRequest: The URL request. + - parameter stream: The stream to upload. + + - returns: The created upload request. +*/ +public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request { + return Manager.sharedInstance.upload(URLRequest, stream: stream) +} + +// MARK: MultipartFormData + +/** + Creates an upload request using the shared manager instance for the specified method and URL string. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + `MultipartFormDataEncodingMemoryThreshold` by default. + - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. +*/ +public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + multipartFormData: MultipartFormData -> Void, + encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold, + encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?) +{ + return Manager.sharedInstance.upload( + method, + URLString, + headers: headers, + multipartFormData: multipartFormData, + encodingMemoryThreshold: encodingMemoryThreshold, + encodingCompletion: encodingCompletion + ) +} + +/** + Creates an upload request using the shared manager instance for the specified method and URL string. + + - parameter URLRequest: The URL request. + - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + `MultipartFormDataEncodingMemoryThreshold` by default. + - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. +*/ +public func upload( + URLRequest: URLRequestConvertible, + multipartFormData: MultipartFormData -> Void, + encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold, + encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?) +{ + return Manager.sharedInstance.upload( + URLRequest, + multipartFormData: multipartFormData, + encodingMemoryThreshold: encodingMemoryThreshold, + encodingCompletion: encodingCompletion + ) +} + +// MARK: - Download Methods + +// MARK: URL Request + +/** + Creates a download request using the shared manager instance for the specified method and URL string. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter parameters: The parameters. `nil` by default. + - parameter encoding: The parameter encoding. `.URL` by default. + - parameter headers: The HTTP headers. `nil` by default. + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. +*/ +public func download( + method: Method, + _ URLString: URLStringConvertible, + parameters: [String: AnyObject]? = nil, + encoding: ParameterEncoding = .URL, + headers: [String: String]? = nil, + destination: Request.DownloadFileDestination) + -> Request +{ + return Manager.sharedInstance.download( + method, + URLString, + parameters: parameters, + encoding: encoding, + headers: headers, + destination: destination + ) +} + +/** + Creates a download request using the shared manager instance for the specified URL request. + + - parameter URLRequest: The URL request. + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. +*/ +public func download(URLRequest: URLRequestConvertible, destination: Request.DownloadFileDestination) -> Request { + return Manager.sharedInstance.download(URLRequest, destination: destination) +} + +// MARK: Resume Data + +/** + Creates a request using the shared manager instance for downloading from the resume data produced from a + previous request cancellation. + + - parameter resumeData: The resume data. This is an opaque data blob produced by `NSURLSessionDownloadTask` + when a task is cancelled. See `NSURLSession -downloadTaskWithResumeData:` for additional + information. + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. +*/ +public func download(resumeData data: NSData, destination: Request.DownloadFileDestination) -> Request { + return Manager.sharedInstance.download(data, destination: destination) +} diff --git a/Pods/Alamofire/Source/Download.swift b/Pods/Alamofire/Source/Download.swift new file mode 100644 index 0000000..1df90cc --- /dev/null +++ b/Pods/Alamofire/Source/Download.swift @@ -0,0 +1,244 @@ +// Download.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +extension Manager { + private enum Downloadable { + case Request(NSURLRequest) + case ResumeData(NSData) + } + + private func download(downloadable: Downloadable, destination: Request.DownloadFileDestination) -> Request { + var downloadTask: NSURLSessionDownloadTask! + + switch downloadable { + case .Request(let request): + dispatch_sync(queue) { + downloadTask = self.session.downloadTaskWithRequest(request) + } + case .ResumeData(let resumeData): + dispatch_sync(queue) { + downloadTask = self.session.downloadTaskWithResumeData(resumeData) + } + } + + let request = Request(session: session, task: downloadTask) + + if let downloadDelegate = request.delegate as? Request.DownloadTaskDelegate { + downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, downloadTask, URL in + return destination(URL, downloadTask.response as! NSHTTPURLResponse) + } + } + + delegate[request.delegate.task] = request.delegate + + if startRequestsImmediately { + request.resume() + } + + return request + } + + // MARK: Request + + /** + Creates a download request for the specified method, URL string, parameters, parameter encoding, headers + and destination. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter parameters: The parameters. `nil` by default. + - parameter encoding: The parameter encoding. `.URL` by default. + - parameter headers: The HTTP headers. `nil` by default. + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. + */ + public func download( + method: Method, + _ URLString: URLStringConvertible, + parameters: [String: AnyObject]? = nil, + encoding: ParameterEncoding = .URL, + headers: [String: String]? = nil, + destination: Request.DownloadFileDestination) + -> Request + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0 + + return download(encodedURLRequest, destination: destination) + } + + /** + Creates a request for downloading from the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. + */ + public func download(URLRequest: URLRequestConvertible, destination: Request.DownloadFileDestination) -> Request { + return download(.Request(URLRequest.URLRequest), destination: destination) + } + + // MARK: Resume Data + + /** + Creates a request for downloading from the resume data produced from a previous request cancellation. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter resumeData: The resume data. This is an opaque data blob produced by `NSURLSessionDownloadTask` + when a task is cancelled. See `NSURLSession -downloadTaskWithResumeData:` for + additional information. + - parameter destination: The closure used to determine the destination of the downloaded file. + + - returns: The created download request. + */ + public func download(resumeData: NSData, destination: Request.DownloadFileDestination) -> Request { + return download(.ResumeData(resumeData), destination: destination) + } +} + +// MARK: - + +extension Request { + /** + A closure executed once a request has successfully completed in order to determine where to move the temporary + file written to during the download process. The closure takes two arguments: the temporary file URL and the URL + response, and returns a single argument: the file URL where the temporary file should be moved. + */ + public typealias DownloadFileDestination = (NSURL, NSHTTPURLResponse) -> NSURL + + /** + Creates a download file destination closure which uses the default file manager to move the temporary file to a + file URL in the first available directory with the specified search path directory and search path domain mask. + + - parameter directory: The search path directory. `.DocumentDirectory` by default. + - parameter domain: The search path domain mask. `.UserDomainMask` by default. + + - returns: A download file destination closure. + */ + public class func suggestedDownloadDestination( + directory directory: NSSearchPathDirectory = .DocumentDirectory, + domain: NSSearchPathDomainMask = .UserDomainMask) + -> DownloadFileDestination + { + return { temporaryURL, response -> NSURL in + let directoryURLs = NSFileManager.defaultManager().URLsForDirectory(directory, inDomains: domain) + + if !directoryURLs.isEmpty { + return directoryURLs[0].URLByAppendingPathComponent(response.suggestedFilename!) + } + + return temporaryURL + } + } + + /// The resume data of the underlying download task if available after a failure. + public var resumeData: NSData? { + var data: NSData? + + if let delegate = delegate as? DownloadTaskDelegate { + data = delegate.resumeData + } + + return data + } + + // MARK: - DownloadTaskDelegate + + class DownloadTaskDelegate: TaskDelegate, NSURLSessionDownloadDelegate { + var downloadTask: NSURLSessionDownloadTask? { return task as? NSURLSessionDownloadTask } + var downloadProgress: ((Int64, Int64, Int64) -> Void)? + + var resumeData: NSData? + override var data: NSData? { return resumeData } + + // MARK: - NSURLSessionDownloadDelegate + + // MARK: Override Closures + + var downloadTaskDidFinishDownloadingToURL: ((NSURLSession, NSURLSessionDownloadTask, NSURL) -> NSURL)? + var downloadTaskDidWriteData: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + var downloadTaskDidResumeAtOffset: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64) -> Void)? + + // MARK: Delegate Methods + + func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didFinishDownloadingToURL location: NSURL) + { + if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL { + do { + let destination = downloadTaskDidFinishDownloadingToURL(session, downloadTask, location) + try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destination) + } catch { + self.error = error as NSError + } + } + } + + func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + { + if let downloadTaskDidWriteData = downloadTaskDidWriteData { + downloadTaskDidWriteData( + session, + downloadTask, + bytesWritten, + totalBytesWritten, + totalBytesExpectedToWrite + ) + } else { + progress.totalUnitCount = totalBytesExpectedToWrite + progress.completedUnitCount = totalBytesWritten + + downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } + } + + func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + { + if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { + downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) + } else { + progress.totalUnitCount = expectedTotalBytes + progress.completedUnitCount = fileOffset + } + } + } +} diff --git a/Pods/Alamofire/Source/Error.swift b/Pods/Alamofire/Source/Error.swift new file mode 100644 index 0000000..b776a3e --- /dev/null +++ b/Pods/Alamofire/Source/Error.swift @@ -0,0 +1,66 @@ +// Error.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// The `Error` struct provides a convenience for creating custom Alamofire NSErrors. +public struct Error { + /// The domain used for creating all Alamofire errors. + public static let Domain = "com.alamofire.error" + + /// The custom error codes generated by Alamofire. + public enum Code: Int { + case InputStreamReadFailed = -6000 + case OutputStreamWriteFailed = -6001 + case ContentTypeValidationFailed = -6002 + case StatusCodeValidationFailed = -6003 + case DataSerializationFailed = -6004 + case StringSerializationFailed = -6005 + case JSONSerializationFailed = -6006 + case PropertyListSerializationFailed = -6007 + } + + /** + Creates an `NSError` with the given error code and failure reason. + + - parameter code: The error code. + - parameter failureReason: The failure reason. + + - returns: An `NSError` with the given error code and failure reason. + */ + public static func errorWithCode(code: Code, failureReason: String) -> NSError { + return errorWithCode(code.rawValue, failureReason: failureReason) + } + + /** + Creates an `NSError` with the given error code and failure reason. + + - parameter code: The error code. + - parameter failureReason: The failure reason. + + - returns: An `NSError` with the given error code and failure reason. + */ + public static func errorWithCode(code: Int, failureReason: String) -> NSError { + let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason] + return NSError(domain: Domain, code: code, userInfo: userInfo) + } +} diff --git a/Pods/Alamofire/Source/Manager.swift b/Pods/Alamofire/Source/Manager.swift new file mode 100644 index 0000000..5dc87d9 --- /dev/null +++ b/Pods/Alamofire/Source/Manager.swift @@ -0,0 +1,708 @@ +// Manager.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`. +*/ +public class Manager { + + // MARK: - Properties + + /** + A shared instance of `Manager`, used by top-level Alamofire request methods, and suitable for use directly + for any ad hoc requests. + */ + public static let sharedInstance: Manager = { + let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() + configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders + + return Manager(configuration: configuration) + }() + + /** + Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers. + */ + public static let defaultHTTPHeaders: [String: String] = { + // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3 + let acceptEncoding: String = "gzip;q=1.0,compress;q=0.5" + + // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5 + let acceptLanguage: String = { + var components: [String] = [] + for (index, languageCode) in (NSLocale.preferredLanguages() as [String]).enumerate() { + let q = 1.0 - (Double(index) * 0.1) + components.append("\(languageCode);q=\(q)") + if q <= 0.5 { + break + } + } + + return components.joinWithSeparator(",") + }() + + // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3 + let userAgent: String = { + if let info = NSBundle.mainBundle().infoDictionary { + let executable: AnyObject = info[kCFBundleExecutableKey as String] ?? "Unknown" + let bundle: AnyObject = info[kCFBundleIdentifierKey as String] ?? "Unknown" + let version: AnyObject = info[kCFBundleVersionKey as String] ?? "Unknown" + let os: AnyObject = NSProcessInfo.processInfo().operatingSystemVersionString ?? "Unknown" + + var mutableUserAgent = NSMutableString(string: "\(executable)/\(bundle) (\(version); OS \(os))") as CFMutableString + let transform = NSString(string: "Any-Latin; Latin-ASCII; [:^ASCII:] Remove") as CFString + + if CFStringTransform(mutableUserAgent, UnsafeMutablePointer(nil), transform, false) { + return mutableUserAgent as String + } + } + + return "Alamofire" + }() + + return [ + "Accept-Encoding": acceptEncoding, + "Accept-Language": acceptLanguage, + "User-Agent": userAgent + ] + }() + + let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL) + + /// The underlying session. + public let session: NSURLSession + + /// The session delegate handling all the task and session delegate callbacks. + public let delegate: SessionDelegate + + /// Whether to start requests immediately after being constructed. `true` by default. + public var startRequestsImmediately: Bool = true + + /** + The background completion handler closure provided by the UIApplicationDelegate + `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background + completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation + will automatically call the handler. + + If you need to handle your own events before the handler is called, then you need to override the + SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished. + + `nil` by default. + */ + public var backgroundCompletionHandler: (() -> Void)? + + // MARK: - Lifecycle + + /** + Initializes the `Manager` instance with the specified configuration, delegate and server trust policy. + + - parameter configuration: The configuration used to construct the managed session. + `NSURLSessionConfiguration.defaultSessionConfiguration()` by default. + - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by + default. + - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust + challenges. `nil` by default. + + - returns: The new `Manager` instance. + */ + public init( + configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration(), + delegate: SessionDelegate = SessionDelegate(), + serverTrustPolicyManager: ServerTrustPolicyManager? = nil) + { + self.delegate = delegate + self.session = NSURLSession(configuration: configuration, delegate: delegate, delegateQueue: nil) + + commonInit(serverTrustPolicyManager: serverTrustPolicyManager) + } + + /** + Initializes the `Manager` instance with the specified session, delegate and server trust policy. + + - parameter session: The URL session. + - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate. + - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust + challenges. `nil` by default. + + - returns: The new `Manager` instance if the URL session's delegate matches the delegate parameter. + */ + public init?( + session: NSURLSession, + delegate: SessionDelegate, + serverTrustPolicyManager: ServerTrustPolicyManager? = nil) + { + self.delegate = delegate + self.session = session + + guard delegate === session.delegate else { return nil } + + commonInit(serverTrustPolicyManager: serverTrustPolicyManager) + } + + private func commonInit(serverTrustPolicyManager serverTrustPolicyManager: ServerTrustPolicyManager?) { + session.serverTrustPolicyManager = serverTrustPolicyManager + + delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in + guard let strongSelf = self else { return } + dispatch_async(dispatch_get_main_queue()) { strongSelf.backgroundCompletionHandler?() } + } + } + + deinit { + session.invalidateAndCancel() + } + + // MARK: - Request + + /** + Creates a request for the specified method, URL string, parameters, parameter encoding and headers. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter parameters: The parameters. `nil` by default. + - parameter encoding: The parameter encoding. `.URL` by default. + - parameter headers: The HTTP headers. `nil` by default. + + - returns: The created request. + */ + public func request( + method: Method, + _ URLString: URLStringConvertible, + parameters: [String: AnyObject]? = nil, + encoding: ParameterEncoding = .URL, + headers: [String: String]? = nil) + -> Request + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0 + return request(encodedURLRequest) + } + + /** + Creates a request for the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request + + - returns: The created request. + */ + public func request(URLRequest: URLRequestConvertible) -> Request { + var dataTask: NSURLSessionDataTask! + + dispatch_sync(queue) { + dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest) + } + + let request = Request(session: session, task: dataTask) + delegate[request.delegate.task] = request.delegate + + if startRequestsImmediately { + request.resume() + } + + return request + } + + // MARK: - SessionDelegate + + /** + Responsible for handling all delegate callbacks for the underlying session. + */ + public final class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate { + private var subdelegates: [Int: Request.TaskDelegate] = [:] + private let subdelegateQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT) + + subscript(task: NSURLSessionTask) -> Request.TaskDelegate? { + get { + var subdelegate: Request.TaskDelegate? + dispatch_sync(subdelegateQueue) { + subdelegate = self.subdelegates[task.taskIdentifier] + } + + return subdelegate + } + + set { + dispatch_barrier_async(subdelegateQueue) { + self.subdelegates[task.taskIdentifier] = newValue + } + } + } + + /** + Initializes the `SessionDelegate` instance. + + - returns: The new `SessionDelegate` instance. + */ + public override init() { + super.init() + } + + // MARK: - NSURLSessionDelegate + + // MARK: Override Closures + + /// Overrides default behavior for NSURLSessionDelegate method `URLSession:didBecomeInvalidWithError:`. + public var sessionDidBecomeInvalidWithError: ((NSURLSession, NSError?) -> Void)? + + /// Overrides default behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:`. + public var sessionDidReceiveChallenge: ((NSURLSession, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))? + + /// Overrides default behavior for NSURLSessionDelegate method `URLSessionDidFinishEventsForBackgroundURLSession:`. + public var sessionDidFinishEventsForBackgroundURLSession: ((NSURLSession) -> Void)? + + // MARK: Delegate Methods + + /** + Tells the delegate that the session has been invalidated. + + - parameter session: The session object that was invalidated. + - parameter error: The error that caused invalidation, or nil if the invalidation was explicit. + */ + public func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) { + sessionDidBecomeInvalidWithError?(session, error) + } + + /** + Requests credentials from the delegate in response to a session-level authentication request from the remote server. + + - parameter session: The session containing the task that requested authentication. + - parameter challenge: An object that contains the request for authentication. + - parameter completionHandler: A handler that your delegate method must call providing the disposition and credential. + */ + public func URLSession( + session: NSURLSession, + didReceiveChallenge challenge: NSURLAuthenticationChallenge, + completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)) + { + var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling + var credential: NSURLCredential? + + if let sessionDidReceiveChallenge = sessionDidReceiveChallenge { + (disposition, credential) = sessionDidReceiveChallenge(session, challenge) + } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + let host = challenge.protectionSpace.host + + if let + serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host), + serverTrust = challenge.protectionSpace.serverTrust + { + if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) { + disposition = .UseCredential + credential = NSURLCredential(forTrust: serverTrust) + } else { + disposition = .CancelAuthenticationChallenge + } + } + } + + completionHandler(disposition, credential) + } + + /** + Tells the delegate that all messages enqueued for a session have been delivered. + + - parameter session: The session that no longer has any outstanding requests. + */ + public func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) { + sessionDidFinishEventsForBackgroundURLSession?(session) + } + + // MARK: - NSURLSessionTaskDelegate + + // MARK: Override Closures + + /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:`. + public var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)? + + /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didReceiveChallenge:completionHandler:`. + public var taskDidReceiveChallenge: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))? + + /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:session:task:needNewBodyStream:`. + public var taskNeedNewBodyStream: ((NSURLSession, NSURLSessionTask) -> NSInputStream!)? + + /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`. + public var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)? + + /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didCompleteWithError:`. + public var taskDidComplete: ((NSURLSession, NSURLSessionTask, NSError?) -> Void)? + + // MARK: Delegate Methods + + /** + Tells the delegate that the remote server requested an HTTP redirect. + + - parameter session: The session containing the task whose request resulted in a redirect. + - parameter task: The task whose request resulted in a redirect. + - parameter response: An object containing the server’s response to the original request. + - parameter request: A URL request object filled out with the new location. + - parameter completionHandler: A closure that your handler should call with either the value of the request + parameter, a modified URL request object, or NULL to refuse the redirect and + return the body of the redirect response. + */ + public func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + willPerformHTTPRedirection response: NSHTTPURLResponse, + newRequest request: NSURLRequest, + completionHandler: ((NSURLRequest?) -> Void)) + { + var redirectRequest: NSURLRequest? = request + + if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { + redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) + } + + completionHandler(redirectRequest) + } + + /** + Requests credentials from the delegate in response to an authentication request from the remote server. + + - parameter session: The session containing the task whose request requires authentication. + - parameter task: The task whose request requires authentication. + - parameter challenge: An object that contains the request for authentication. + - parameter completionHandler: A handler that your delegate method must call providing the disposition and credential. + */ + public func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + didReceiveChallenge challenge: NSURLAuthenticationChallenge, + completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)) + { + if let taskDidReceiveChallenge = taskDidReceiveChallenge { + completionHandler(taskDidReceiveChallenge(session, task, challenge)) + } else if let delegate = self[task] { + delegate.URLSession( + session, + task: task, + didReceiveChallenge: challenge, + completionHandler: completionHandler + ) + } else { + URLSession(session, didReceiveChallenge: challenge, completionHandler: completionHandler) + } + } + + /** + Tells the delegate when a task requires a new request body stream to send to the remote server. + + - parameter session: The session containing the task that needs a new body stream. + - parameter task: The task that needs a new body stream. + - parameter completionHandler: A completion handler that your delegate method should call with the new body stream. + */ + public func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + needNewBodyStream completionHandler: ((NSInputStream?) -> Void)) + { + if let taskNeedNewBodyStream = taskNeedNewBodyStream { + completionHandler(taskNeedNewBodyStream(session, task)) + } else if let delegate = self[task] { + delegate.URLSession(session, task: task, needNewBodyStream: completionHandler) + } + } + + /** + Periodically informs the delegate of the progress of sending body content to the server. + + - parameter session: The session containing the data task. + - parameter task: The data task. + - parameter bytesSent: The number of bytes sent since the last time this delegate method was called. + - parameter totalBytesSent: The total number of bytes sent so far. + - parameter totalBytesExpectedToSend: The expected length of the body data. + */ + public func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + { + if let taskDidSendBodyData = taskDidSendBodyData { + taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } else if let delegate = self[task] as? Request.UploadTaskDelegate { + delegate.URLSession( + session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend + ) + } + } + + /** + Tells the delegate that the task finished transferring data. + + - parameter session: The session containing the task whose request finished transferring data. + - parameter task: The task whose request finished transferring data. + - parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil. + */ + public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) { + if let taskDidComplete = taskDidComplete { + taskDidComplete(session, task, error) + } else if let delegate = self[task] { + delegate.URLSession(session, task: task, didCompleteWithError: error) + } + + self[task] = nil + } + + // MARK: - NSURLSessionDataDelegate + + // MARK: Override Closures + + /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveResponse:completionHandler:`. + public var dataTaskDidReceiveResponse: ((NSURLSession, NSURLSessionDataTask, NSURLResponse) -> NSURLSessionResponseDisposition)? + + /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didBecomeDownloadTask:`. + public var dataTaskDidBecomeDownloadTask: ((NSURLSession, NSURLSessionDataTask, NSURLSessionDownloadTask) -> Void)? + + /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveData:`. + public var dataTaskDidReceiveData: ((NSURLSession, NSURLSessionDataTask, NSData) -> Void)? + + /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:`. + public var dataTaskWillCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse!)? + + // MARK: Delegate Methods + + /** + Tells the delegate that the data task received the initial reply (headers) from the server. + + - parameter session: The session containing the data task that received an initial reply. + - parameter dataTask: The data task that received an initial reply. + - parameter response: A URL response object populated with headers. + - parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a + constant to indicate whether the transfer should continue as a data task or + should become a download task. + */ + public func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + didReceiveResponse response: NSURLResponse, + completionHandler: ((NSURLSessionResponseDisposition) -> Void)) + { + var disposition: NSURLSessionResponseDisposition = .Allow + + if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { + disposition = dataTaskDidReceiveResponse(session, dataTask, response) + } + + completionHandler(disposition) + } + + /** + Tells the delegate that the data task was changed to a download task. + + - parameter session: The session containing the task that was replaced by a download task. + - parameter dataTask: The data task that was replaced by a download task. + - parameter downloadTask: The new download task that replaced the data task. + */ + public func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) + { + if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask { + dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask) + } else { + let downloadDelegate = Request.DownloadTaskDelegate(task: downloadTask) + self[downloadTask] = downloadDelegate + } + } + + /** + Tells the delegate that the data task has received some of the expected data. + + - parameter session: The session containing the data task that provided data. + - parameter dataTask: The data task that provided data. + - parameter data: A data object containing the transferred data. + */ + public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + if let dataTaskDidReceiveData = dataTaskDidReceiveData { + dataTaskDidReceiveData(session, dataTask, data) + } else if let delegate = self[dataTask] as? Request.DataTaskDelegate { + delegate.URLSession(session, dataTask: dataTask, didReceiveData: data) + } + } + + /** + Asks the delegate whether the data (or upload) task should store the response in the cache. + + - parameter session: The session containing the data (or upload) task. + - parameter dataTask: The data (or upload) task. + - parameter proposedResponse: The default caching behavior. This behavior is determined based on the current + caching policy and the values of certain received headers, such as the Pragma + and Cache-Control headers. + - parameter completionHandler: A block that your handler must call, providing either the original proposed + response, a modified version of that response, or NULL to prevent caching the + response. If your delegate implements this method, it must call this completion + handler; otherwise, your app leaks memory. + */ + public func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + willCacheResponse proposedResponse: NSCachedURLResponse, + completionHandler: ((NSCachedURLResponse?) -> Void)) + { + if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { + completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse)) + } else if let delegate = self[dataTask] as? Request.DataTaskDelegate { + delegate.URLSession( + session, + dataTask: dataTask, + willCacheResponse: proposedResponse, + completionHandler: completionHandler + ) + } else { + completionHandler(proposedResponse) + } + } + + // MARK: - NSURLSessionDownloadDelegate + + // MARK: Override Closures + + /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didFinishDownloadingToURL:`. + public var downloadTaskDidFinishDownloadingToURL: ((NSURLSession, NSURLSessionDownloadTask, NSURL) -> Void)? + + /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:`. + public var downloadTaskDidWriteData: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + + /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`. + public var downloadTaskDidResumeAtOffset: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64) -> Void)? + + // MARK: Delegate Methods + + /** + Tells the delegate that a download task has finished downloading. + + - parameter session: The session containing the download task that finished. + - parameter downloadTask: The download task that finished. + - parameter location: A file URL for the temporary file. Because the file is temporary, you must either + open the file for reading or move it to a permanent location in your app’s sandbox + container directory before returning from this delegate method. + */ + public func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didFinishDownloadingToURL location: NSURL) + { + if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL { + downloadTaskDidFinishDownloadingToURL(session, downloadTask, location) + } else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate { + delegate.URLSession(session, downloadTask: downloadTask, didFinishDownloadingToURL: location) + } + } + + /** + Periodically informs the delegate about the download’s progress. + + - parameter session: The session containing the download task. + - parameter downloadTask: The download task. + - parameter bytesWritten: The number of bytes transferred since the last time this delegate + method was called. + - parameter totalBytesWritten: The total number of bytes transferred so far. + - parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length + header. If this header was not provided, the value is + `NSURLSessionTransferSizeUnknown`. + */ + public func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + { + if let downloadTaskDidWriteData = downloadTaskDidWriteData { + downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate { + delegate.URLSession( + session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite + ) + } + } + + /** + Tells the delegate that the download task has resumed downloading. + + - parameter session: The session containing the download task that finished. + - parameter downloadTask: The download task that resumed. See explanation in the discussion. + - parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the + existing content, then this value is zero. Otherwise, this value is an + integer representing the number of bytes on disk that do not need to be + retrieved again. + - parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header. + If this header was not provided, the value is NSURLSessionTransferSizeUnknown. + */ + public func URLSession( + session: NSURLSession, + downloadTask: NSURLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + { + if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { + downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) + } else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate { + delegate.URLSession( + session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes + ) + } + } + + // MARK: - NSURLSessionStreamDelegate + + var _streamTaskReadClosed: Any? + var _streamTaskWriteClosed: Any? + var _streamTaskBetterRouteDiscovered: Any? + var _streamTaskDidBecomeInputStream: Any? + + // MARK: - NSObject + + public override func respondsToSelector(selector: Selector) -> Bool { + switch selector { + case "URLSession:didBecomeInvalidWithError:": + return sessionDidBecomeInvalidWithError != nil + case "URLSession:didReceiveChallenge:completionHandler:": + return sessionDidReceiveChallenge != nil + case "URLSessionDidFinishEventsForBackgroundURLSession:": + return sessionDidFinishEventsForBackgroundURLSession != nil + case "URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:": + return taskWillPerformHTTPRedirection != nil + case "URLSession:dataTask:didReceiveResponse:completionHandler:": + return dataTaskDidReceiveResponse != nil + default: + return self.dynamicType.instancesRespondToSelector(selector) + } + } + } +} diff --git a/Pods/Alamofire/Source/MultipartFormData.swift b/Pods/Alamofire/Source/MultipartFormData.swift new file mode 100644 index 0000000..d798f9c --- /dev/null +++ b/Pods/Alamofire/Source/MultipartFormData.swift @@ -0,0 +1,669 @@ +// MultipartFormData.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +#if os(iOS) || os(watchOS) || os(tvOS) +import MobileCoreServices +#elseif os(OSX) +import CoreServices +#endif + +/** + Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode + multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead + to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the + data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for + larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset. + + For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well + and the w3 form documentation. + + - https://www.ietf.org/rfc/rfc2388.txt + - https://www.ietf.org/rfc/rfc2045.txt + - https://www.w3.org/TR/html401/interact/forms.html#h-17.13 +*/ +public class MultipartFormData { + + // MARK: - Helper Types + + struct EncodingCharacters { + static let CRLF = "\r\n" + } + + struct BoundaryGenerator { + enum BoundaryType { + case Initial, Encapsulated, Final + } + + static func randomBoundary() -> String { + return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random()) + } + + static func boundaryData(boundaryType boundaryType: BoundaryType, boundary: String) -> NSData { + let boundaryText: String + + switch boundaryType { + case .Initial: + boundaryText = "--\(boundary)\(EncodingCharacters.CRLF)" + case .Encapsulated: + boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)\(EncodingCharacters.CRLF)" + case .Final: + boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)--\(EncodingCharacters.CRLF)" + } + + return boundaryText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + } + } + + class BodyPart { + let headers: [String: String] + let bodyStream: NSInputStream + let bodyContentLength: UInt64 + var hasInitialBoundary = false + var hasFinalBoundary = false + + init(headers: [String: String], bodyStream: NSInputStream, bodyContentLength: UInt64) { + self.headers = headers + self.bodyStream = bodyStream + self.bodyContentLength = bodyContentLength + } + } + + // MARK: - Properties + + /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`. + public var contentType: String { return "multipart/form-data; boundary=\(boundary)" } + + /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries. + public var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } } + + /// The boundary used to separate the body parts in the encoded form data. + public let boundary: String + + private var bodyParts: [BodyPart] + private var bodyPartError: NSError? + private let streamBufferSize: Int + + // MARK: - Lifecycle + + /** + Creates a multipart form data object. + + - returns: The multipart form data object. + */ + public init() { + self.boundary = BoundaryGenerator.randomBoundary() + self.bodyParts = [] + + /** + * The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more + * information, please refer to the following article: + * - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html + */ + + self.streamBufferSize = 1024 + } + + // MARK: - Body Parts + + /** + Creates a body part from the data and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - `Content-Disposition: form-data; name=#{name}` (HTTP Header) + - Encoded data + - Multipart form boundary + + - parameter data: The data to encode into the multipart form data. + - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + */ + public func appendBodyPart(data data: NSData, name: String) { + let headers = contentHeaders(name: name) + let stream = NSInputStream(data: data) + let length = UInt64(data.length) + + appendBodyPart(stream: stream, length: length, headers: headers) + } + + /** + Creates a body part from the data and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - `Content-Disposition: form-data; name=#{name}` (HTTP Header) + - `Content-Type: #{generated mimeType}` (HTTP Header) + - Encoded data + - Multipart form boundary + + - parameter data: The data to encode into the multipart form data. + - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + - parameter mimeType: The MIME type to associate with the data content type in the `Content-Type` HTTP header. + */ + public func appendBodyPart(data data: NSData, name: String, mimeType: String) { + let headers = contentHeaders(name: name, mimeType: mimeType) + let stream = NSInputStream(data: data) + let length = UInt64(data.length) + + appendBodyPart(stream: stream, length: length, headers: headers) + } + + /** + Creates a body part from the data and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + - `Content-Type: #{mimeType}` (HTTP Header) + - Encoded file data + - Multipart form boundary + + - parameter data: The data to encode into the multipart form data. + - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header. + - parameter fileName: The filename to associate with the data in the `Content-Disposition` HTTP header. + - parameter mimeType: The MIME type to associate with the data in the `Content-Type` HTTP header. + */ + public func appendBodyPart(data data: NSData, name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType) + let stream = NSInputStream(data: data) + let length = UInt64(data.length) + + appendBodyPart(stream: stream, length: length, headers: headers) + } + + /** + Creates a body part from the file and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header) + - `Content-Type: #{generated mimeType}` (HTTP Header) + - Encoded file data + - Multipart form boundary + + The filename in the `Content-Disposition` HTTP header is generated from the last path component of the + `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the + system associated MIME type. + + - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data. + - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header. + */ + public func appendBodyPart(fileURL fileURL: NSURL, name: String) { + if let + fileName = fileURL.lastPathComponent, + pathExtension = fileURL.pathExtension + { + let mimeType = mimeTypeForPathExtension(pathExtension) + appendBodyPart(fileURL: fileURL, name: name, fileName: fileName, mimeType: mimeType) + } else { + let failureReason = "Failed to extract the fileName of the provided URL: \(fileURL)" + setBodyPartError(Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)) + } + } + + /** + Creates a body part from the file and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header) + - Content-Type: #{mimeType} (HTTP Header) + - Encoded file data + - Multipart form boundary + + - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data. + - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header. + - parameter fileName: The filename to associate with the file content in the `Content-Disposition` HTTP header. + - parameter mimeType: The MIME type to associate with the file content in the `Content-Type` HTTP header. + */ + public func appendBodyPart(fileURL fileURL: NSURL, name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType) + + //============================================================ + // Check 1 - is file URL? + //============================================================ + + guard fileURL.fileURL else { + let failureReason = "The file URL does not point to a file URL: \(fileURL)" + let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason) + setBodyPartError(error) + return + } + + //============================================================ + // Check 2 - is file URL reachable? + //============================================================ + + var isReachable = true + + if #available(OSX 10.10, *) { + isReachable = fileURL.checkPromisedItemIsReachableAndReturnError(nil) + } + + guard isReachable else { + let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)") + setBodyPartError(error) + return + } + + //============================================================ + // Check 3 - is file URL a directory? + //============================================================ + + var isDirectory: ObjCBool = false + + guard let + path = fileURL.path + where NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDirectory) && !isDirectory else + { + let failureReason = "The file URL is a directory, not a file: \(fileURL)" + let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason) + setBodyPartError(error) + return + } + + //============================================================ + // Check 4 - can the file size be extracted? + //============================================================ + + var bodyContentLength: UInt64? + + do { + if let + path = fileURL.path, + fileSize = try NSFileManager.defaultManager().attributesOfItemAtPath(path)[NSFileSize] as? NSNumber + { + bodyContentLength = fileSize.unsignedLongLongValue + } + } catch { + // No-op + } + + guard let length = bodyContentLength else { + let failureReason = "Could not fetch attributes from the file URL: \(fileURL)" + let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason) + setBodyPartError(error) + return + } + + //============================================================ + // Check 5 - can a stream be created from file URL? + //============================================================ + + guard let stream = NSInputStream(URL: fileURL) else { + let failureReason = "Failed to create an input stream from the file URL: \(fileURL)" + let error = Error.errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason) + setBodyPartError(error) + return + } + + appendBodyPart(stream: stream, length: length, headers: headers) + } + + /** + Creates a body part from the stream and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + - `Content-Type: #{mimeType}` (HTTP Header) + - Encoded stream data + - Multipart form boundary + + - parameter stream: The input stream to encode in the multipart form data. + - parameter length: The content length of the stream. + - parameter name: The name to associate with the stream content in the `Content-Disposition` HTTP header. + - parameter fileName: The filename to associate with the stream content in the `Content-Disposition` HTTP header. + - parameter mimeType: The MIME type to associate with the stream content in the `Content-Type` HTTP header. + */ + public func appendBodyPart( + stream stream: NSInputStream, + length: UInt64, + name: String, + fileName: String, + mimeType: String) + { + let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType) + appendBodyPart(stream: stream, length: length, headers: headers) + } + + /** + Creates a body part with the headers, stream and length and appends it to the multipart form data object. + + The body part data will be encoded using the following format: + + - HTTP headers + - Encoded stream data + - Multipart form boundary + + - parameter stream: The input stream to encode in the multipart form data. + - parameter length: The content length of the stream. + - parameter headers: The HTTP headers for the body part. + */ + public func appendBodyPart(stream stream: NSInputStream, length: UInt64, headers: [String: String]) { + let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) + bodyParts.append(bodyPart) + } + + // MARK: - Data Encoding + + /** + Encodes all the appended body parts into a single `NSData` object. + + It is important to note that this method will load all the appended body parts into memory all at the same + time. This method should only be used when the encoded data will have a small memory footprint. For large data + cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method. + + - throws: An `NSError` if encoding encounters an error. + + - returns: The encoded `NSData` if encoding is successful. + */ + public func encode() throws -> NSData { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + let encoded = NSMutableData() + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + let encodedData = try encodeBodyPart(bodyPart) + encoded.appendData(encodedData) + } + + return encoded + } + + /** + Writes the appended body parts into the given file URL. + + This process is facilitated by reading and writing with input and output streams, respectively. Thus, + this approach is very memory efficient and should be used for large body part data. + + - parameter fileURL: The file URL to write the multipart form data into. + + - throws: An `NSError` if encoding encounters an error. + */ + public func writeEncodedDataToDisk(fileURL: NSURL) throws { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + if let path = fileURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) { + let failureReason = "A file already exists at the given file URL: \(fileURL)" + throw Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason) + } else if !fileURL.fileURL { + let failureReason = "The URL does not point to a valid file: \(fileURL)" + throw Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason) + } + + let outputStream: NSOutputStream + + if let possibleOutputStream = NSOutputStream(URL: fileURL, append: false) { + outputStream = possibleOutputStream + } else { + let failureReason = "Failed to create an output stream with the given URL: \(fileURL)" + throw Error.errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason) + } + + outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + outputStream.open() + + self.bodyParts.first?.hasInitialBoundary = true + self.bodyParts.last?.hasFinalBoundary = true + + for bodyPart in self.bodyParts { + try writeBodyPart(bodyPart, toOutputStream: outputStream) + } + + outputStream.close() + outputStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + } + + // MARK: - Private - Body Part Encoding + + private func encodeBodyPart(bodyPart: BodyPart) throws -> NSData { + let encoded = NSMutableData() + + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + encoded.appendData(initialData) + + let headerData = encodeHeaderDataForBodyPart(bodyPart) + encoded.appendData(headerData) + + let bodyStreamData = try encodeBodyStreamDataForBodyPart(bodyPart) + encoded.appendData(bodyStreamData) + + if bodyPart.hasFinalBoundary { + encoded.appendData(finalBoundaryData()) + } + + return encoded + } + + private func encodeHeaderDataForBodyPart(bodyPart: BodyPart) -> NSData { + var headerText = "" + + for (key, value) in bodyPart.headers { + headerText += "\(key): \(value)\(EncodingCharacters.CRLF)" + } + headerText += EncodingCharacters.CRLF + + return headerText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + } + + private func encodeBodyStreamDataForBodyPart(bodyPart: BodyPart) throws -> NSData { + let inputStream = bodyPart.bodyStream + inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream.open() + + var error: NSError? + let encoded = NSMutableData() + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](count: streamBufferSize, repeatedValue: 0) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if inputStream.streamError != nil { + error = inputStream.streamError + break + } + + if bytesRead > 0 { + encoded.appendBytes(buffer, length: bytesRead) + } else if bytesRead < 0 { + let failureReason = "Failed to read from input stream: \(inputStream)" + error = Error.errorWithCode(.InputStreamReadFailed, failureReason: failureReason) + break + } else { + break + } + } + + inputStream.close() + inputStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + + if let error = error { + throw error + } + + return encoded + } + + // MARK: - Private - Writing Body Part to Output Stream + + private func writeBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws { + try writeInitialBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream) + try writeHeaderDataForBodyPart(bodyPart, toOutputStream: outputStream) + try writeBodyStreamForBodyPart(bodyPart, toOutputStream: outputStream) + try writeFinalBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream) + } + + private func writeInitialBoundaryDataForBodyPart( + bodyPart: BodyPart, + toOutputStream outputStream: NSOutputStream) + throws + { + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + return try writeData(initialData, toOutputStream: outputStream) + } + + private func writeHeaderDataForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws { + let headerData = encodeHeaderDataForBodyPart(bodyPart) + return try writeData(headerData, toOutputStream: outputStream) + } + + private func writeBodyStreamForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws { + let inputStream = bodyPart.bodyStream + inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream.open() + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](count: streamBufferSize, repeatedValue: 0) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let streamError = inputStream.streamError { + throw streamError + } + + if bytesRead > 0 { + if buffer.count != bytesRead { + buffer = Array(buffer[0.. 0 { + if outputStream.hasSpaceAvailable { + let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) + + if let streamError = outputStream.streamError { + throw streamError + } + + if bytesWritten < 0 { + let failureReason = "Failed to write to output stream: \(outputStream)" + throw Error.errorWithCode(.OutputStreamWriteFailed, failureReason: failureReason) + } + + bytesToWrite -= bytesWritten + + if bytesToWrite > 0 { + buffer = Array(buffer[bytesWritten.. String { + if let + id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil)?.takeRetainedValue(), + contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() + { + return contentType as String + } + + return "application/octet-stream" + } + + // MARK: - Private - Content Headers + + private func contentHeaders(name name: String) -> [String: String] { + return ["Content-Disposition": "form-data; name=\"\(name)\""] + } + + private func contentHeaders(name name: String, mimeType: String) -> [String: String] { + return [ + "Content-Disposition": "form-data; name=\"\(name)\"", + "Content-Type": "\(mimeType)" + ] + } + + private func contentHeaders(name name: String, fileName: String, mimeType: String) -> [String: String] { + return [ + "Content-Disposition": "form-data; name=\"\(name)\"; filename=\"\(fileName)\"", + "Content-Type": "\(mimeType)" + ] + } + + // MARK: - Private - Boundary Encoding + + private func initialBoundaryData() -> NSData { + return BoundaryGenerator.boundaryData(boundaryType: .Initial, boundary: boundary) + } + + private func encapsulatedBoundaryData() -> NSData { + return BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundary: boundary) + } + + private func finalBoundaryData() -> NSData { + return BoundaryGenerator.boundaryData(boundaryType: .Final, boundary: boundary) + } + + // MARK: - Private - Errors + + private func setBodyPartError(error: NSError) { + if bodyPartError == nil { + bodyPartError = error + } + } +} diff --git a/Pods/Alamofire/Source/ParameterEncoding.swift b/Pods/Alamofire/Source/ParameterEncoding.swift new file mode 100644 index 0000000..0807828 --- /dev/null +++ b/Pods/Alamofire/Source/ParameterEncoding.swift @@ -0,0 +1,251 @@ +// ParameterEncoding.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + HTTP method definitions. + + See https://tools.ietf.org/html/rfc7231#section-4.3 +*/ +public enum Method: String { + case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT +} + +// MARK: ParameterEncoding + +/** + Used to specify the way in which a set of parameters are applied to a URL request. + + - `URL`: Creates a query string to be set as or appended to any existing URL query for `GET`, `HEAD`, + and `DELETE` requests, or set as the body for requests with any other HTTP method. The + `Content-Type` HTTP header field of an encoded request with HTTP body is set to + `application/x-www-form-urlencoded; charset=utf-8`. Since there is no published specification + for how to encode collection types, the convention of appending `[]` to the key for array + values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for nested + dictionary values (`foo[bar]=baz`). + + - `URLEncodedInURL`: Creates query string to be set as or appended to any existing URL query. Uses the same + implementation as the `.URL` case, but always applies the encoded result to the URL. + + - `JSON`: Uses `NSJSONSerialization` to create a JSON representation of the parameters object, which is + set as the body of the request. The `Content-Type` HTTP header field of an encoded request is + set to `application/json`. + + - `PropertyList`: Uses `NSPropertyListSerialization` to create a plist representation of the parameters object, + according to the associated format and write options values, which is set as the body of the + request. The `Content-Type` HTTP header field of an encoded request is set to + `application/x-plist`. + + - `Custom`: Uses the associated closure value to construct a new request given an existing request and + parameters. +*/ +public enum ParameterEncoding { + case URL + case URLEncodedInURL + case JSON + case PropertyList(NSPropertyListFormat, NSPropertyListWriteOptions) + case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?)) + + /** + Creates a URL request by encoding parameters and applying them onto an existing request. + + - parameter URLRequest: The request to have parameters applied + - parameter parameters: The parameters to apply + + - returns: A tuple containing the constructed request and the error that occurred during parameter encoding, + if any. + */ + public func encode( + URLRequest: URLRequestConvertible, + parameters: [String: AnyObject]?) + -> (NSMutableURLRequest, NSError?) + { + var mutableURLRequest = URLRequest.URLRequest + + guard let parameters = parameters else { + return (mutableURLRequest, nil) + } + + var encodingError: NSError? = nil + + switch self { + case .URL, .URLEncodedInURL: + func query(parameters: [String: AnyObject]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys.sort(<) { + let value = parameters[key]! + components += queryComponents(key, value) + } + + return (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&") + } + + func encodesParametersInURL(method: Method) -> Bool { + switch self { + case .URLEncodedInURL: + return true + default: + break + } + + switch method { + case .GET, .HEAD, .DELETE: + return true + default: + return false + } + } + + if let method = Method(rawValue: mutableURLRequest.HTTPMethod) where encodesParametersInURL(method) { + if let URLComponents = NSURLComponents(URL: mutableURLRequest.URL!, resolvingAgainstBaseURL: false) { + let percentEncodedQuery = (URLComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) + URLComponents.percentEncodedQuery = percentEncodedQuery + mutableURLRequest.URL = URLComponents.URL + } + } else { + if mutableURLRequest.valueForHTTPHeaderField("Content-Type") == nil { + mutableURLRequest.setValue( + "application/x-www-form-urlencoded; charset=utf-8", + forHTTPHeaderField: "Content-Type" + ) + } + + mutableURLRequest.HTTPBody = query(parameters).dataUsingEncoding( + NSUTF8StringEncoding, + allowLossyConversion: false + ) + } + case .JSON: + do { + let options = NSJSONWritingOptions() + let data = try NSJSONSerialization.dataWithJSONObject(parameters, options: options) + + mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + mutableURLRequest.HTTPBody = data + } catch { + encodingError = error as NSError + } + case .PropertyList(let format, let options): + do { + let data = try NSPropertyListSerialization.dataWithPropertyList( + parameters, + format: format, + options: options + ) + mutableURLRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type") + mutableURLRequest.HTTPBody = data + } catch { + encodingError = error as NSError + } + case .Custom(let closure): + (mutableURLRequest, encodingError) = closure(mutableURLRequest, parameters) + } + + return (mutableURLRequest, encodingError) + } + + /** + Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion. + + - parameter key: The key of the query component. + - parameter value: The value of the query component. + + - returns: The percent-escaped, URL encoded query string components. + */ + public func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] { + var components: [(String, String)] = [] + + if let dictionary = value as? [String: AnyObject] { + for (nestedKey, value) in dictionary { + components += queryComponents("\(key)[\(nestedKey)]", value) + } + } else if let array = value as? [AnyObject] { + for value in array { + components += queryComponents("\(key)[]", value) + } + } else { + components.append((escape(key), escape("\(value)"))) + } + + return components + } + + /** + Returns a percent-escaped string following RFC 3986 for a query string key or value. + + RFC 3986 states that the following characters are "reserved" characters. + + - General Delimiters: ":", "#", "[", "]", "@", "?", "/" + - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" + + In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow + query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" + should be percent-escaped in the query string. + + - parameter string: The string to be percent-escaped. + + - returns: The percent-escaped string. + */ + public func escape(string: String) -> String { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + + let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet + allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode) + + var escaped = "" + + //========================================================================================================== + // + // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few + // hundred Chinense characters causes various malloc error crashes. To avoid this issue until iOS 8 is no + // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more + // info, please refer to: + // + // - https://github.com/Alamofire/Alamofire/issues/206 + // + //========================================================================================================== + + if #available(iOS 8.3, OSX 10.10, *) { + escaped = string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? string + } else { + let batchSize = 50 + var index = string.startIndex + + while index != string.endIndex { + let startIndex = index + let endIndex = index.advancedBy(batchSize, limit: string.endIndex) + let range = Range(start: startIndex, end: endIndex) + + let substring = string.substringWithRange(range) + + escaped += substring.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? substring + + index = endIndex + } + } + + return escaped + } +} diff --git a/Pods/Alamofire/Source/Request.swift b/Pods/Alamofire/Source/Request.swift new file mode 100644 index 0000000..00932dc --- /dev/null +++ b/Pods/Alamofire/Source/Request.swift @@ -0,0 +1,536 @@ +// Request.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + Responsible for sending a request and receiving the response and associated data from the server, as well as + managing its underlying `NSURLSessionTask`. +*/ +public class Request { + + // MARK: - Properties + + /// The delegate for the underlying task. + public let delegate: TaskDelegate + + /// The underlying task. + public var task: NSURLSessionTask { return delegate.task } + + /// The session belonging to the underlying task. + public let session: NSURLSession + + /// The request sent or to be sent to the server. + public var request: NSURLRequest? { return task.originalRequest } + + /// The response received from the server, if any. + public var response: NSHTTPURLResponse? { return task.response as? NSHTTPURLResponse } + + /// The progress of the request lifecycle. + public var progress: NSProgress { return delegate.progress } + + // MARK: - Lifecycle + + init(session: NSURLSession, task: NSURLSessionTask) { + self.session = session + + switch task { + case is NSURLSessionUploadTask: + self.delegate = UploadTaskDelegate(task: task) + case is NSURLSessionDataTask: + self.delegate = DataTaskDelegate(task: task) + case is NSURLSessionDownloadTask: + self.delegate = DownloadTaskDelegate(task: task) + default: + self.delegate = TaskDelegate(task: task) + } + } + + // MARK: - Authentication + + /** + Associates an HTTP Basic credential with the request. + + - parameter user: The user. + - parameter password: The password. + - parameter persistence: The URL credential persistence. `.ForSession` by default. + + - returns: The request. + */ + public func authenticate( + user user: String, + password: String, + persistence: NSURLCredentialPersistence = .ForSession) + -> Self + { + let credential = NSURLCredential(user: user, password: password, persistence: persistence) + + return authenticate(usingCredential: credential) + } + + /** + Associates a specified credential with the request. + + - parameter credential: The credential. + + - returns: The request. + */ + public func authenticate(usingCredential credential: NSURLCredential) -> Self { + delegate.credential = credential + + return self + } + + // MARK: - Progress + + /** + Sets a closure to be called periodically during the lifecycle of the request as data is written to or read + from the server. + + - For uploads, the progress closure returns the bytes written, total bytes written, and total bytes expected + to write. + - For downloads and data tasks, the progress closure returns the bytes read, total bytes read, and total bytes + expected to read. + + - parameter closure: The code to be executed periodically during the lifecycle of the request. + + - returns: The request. + */ + public func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self { + if let uploadDelegate = delegate as? UploadTaskDelegate { + uploadDelegate.uploadProgress = closure + } else if let dataDelegate = delegate as? DataTaskDelegate { + dataDelegate.dataProgress = closure + } else if let downloadDelegate = delegate as? DownloadTaskDelegate { + downloadDelegate.downloadProgress = closure + } + + return self + } + + /** + Sets a closure to be called periodically during the lifecycle of the request as data is read from the server. + + This closure returns the bytes most recently received from the server, not including data from previous calls. + If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is + also important to note that the `response` closure will be called with nil `responseData`. + + - parameter closure: The code to be executed periodically during the lifecycle of the request. + + - returns: The request. + */ + public func stream(closure: (NSData -> Void)? = nil) -> Self { + if let dataDelegate = delegate as? DataTaskDelegate { + dataDelegate.dataStream = closure + } + + return self + } + + // MARK: - State + + /** + Suspends the request. + */ + public func suspend() { + task.suspend() + } + + /** + Resumes the request. + */ + public func resume() { + task.resume() + } + + /** + Cancels the request. + */ + public func cancel() { + if let + downloadDelegate = delegate as? DownloadTaskDelegate, + downloadTask = downloadDelegate.downloadTask + { + downloadTask.cancelByProducingResumeData { data in + downloadDelegate.resumeData = data + } + } else { + task.cancel() + } + } + + // MARK: - TaskDelegate + + /** + The task delegate is responsible for handling all delegate callbacks for the underlying task as well as + executing all operations attached to the serial operation queue upon task completion. + */ + public class TaskDelegate: NSObject { + + /// The serial operation queue used to execute all operations after the task completes. + public let queue: NSOperationQueue + + let task: NSURLSessionTask + let progress: NSProgress + + var data: NSData? { return nil } + var error: NSError? + + var credential: NSURLCredential? + + init(task: NSURLSessionTask) { + self.task = task + self.progress = NSProgress(totalUnitCount: 0) + self.queue = { + let operationQueue = NSOperationQueue() + operationQueue.maxConcurrentOperationCount = 1 + operationQueue.suspended = true + + if #available(OSX 10.10, *) { + operationQueue.qualityOfService = NSQualityOfService.Utility + } + + return operationQueue + }() + } + + deinit { + queue.cancelAllOperations() + queue.suspended = false + } + + // MARK: - NSURLSessionTaskDelegate + + // MARK: Override Closures + + var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)? + var taskDidReceiveChallenge: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))? + var taskNeedNewBodyStream: ((NSURLSession, NSURLSessionTask) -> NSInputStream?)? + var taskDidCompleteWithError: ((NSURLSession, NSURLSessionTask, NSError?) -> Void)? + + // MARK: Delegate Methods + + func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + willPerformHTTPRedirection response: NSHTTPURLResponse, + newRequest request: NSURLRequest, + completionHandler: ((NSURLRequest?) -> Void)) + { + var redirectRequest: NSURLRequest? = request + + if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { + redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) + } + + completionHandler(redirectRequest) + } + + func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + didReceiveChallenge challenge: NSURLAuthenticationChallenge, + completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)) + { + var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling + var credential: NSURLCredential? + + if let taskDidReceiveChallenge = taskDidReceiveChallenge { + (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) + } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + let host = challenge.protectionSpace.host + + if let + serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host), + serverTrust = challenge.protectionSpace.serverTrust + { + if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) { + disposition = .UseCredential + credential = NSURLCredential(forTrust: serverTrust) + } else { + disposition = .CancelAuthenticationChallenge + } + } + } else { + if challenge.previousFailureCount > 0 { + disposition = .CancelAuthenticationChallenge + } else { + credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace) + + if credential != nil { + disposition = .UseCredential + } + } + } + + completionHandler(disposition, credential) + } + + func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + needNewBodyStream completionHandler: ((NSInputStream?) -> Void)) + { + var bodyStream: NSInputStream? + + if let taskNeedNewBodyStream = taskNeedNewBodyStream { + bodyStream = taskNeedNewBodyStream(session, task) + } + + completionHandler(bodyStream) + } + + func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) { + if let taskDidCompleteWithError = taskDidCompleteWithError { + taskDidCompleteWithError(session, task, error) + } else { + if let error = error { + self.error = error + + if let + downloadDelegate = self as? DownloadTaskDelegate, + userInfo = error.userInfo as? [String: AnyObject], + resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? NSData + { + downloadDelegate.resumeData = resumeData + } + } + + queue.suspended = false + } + } + } + + // MARK: - DataTaskDelegate + + class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate { + var dataTask: NSURLSessionDataTask? { return task as? NSURLSessionDataTask } + + private var totalBytesReceived: Int64 = 0 + private var mutableData: NSMutableData + override var data: NSData? { + if dataStream != nil { + return nil + } else { + return mutableData + } + } + + private var expectedContentLength: Int64? + private var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)? + private var dataStream: ((data: NSData) -> Void)? + + override init(task: NSURLSessionTask) { + mutableData = NSMutableData() + super.init(task: task) + } + + // MARK: - NSURLSessionDataDelegate + + // MARK: Override Closures + + var dataTaskDidReceiveResponse: ((NSURLSession, NSURLSessionDataTask, NSURLResponse) -> NSURLSessionResponseDisposition)? + var dataTaskDidBecomeDownloadTask: ((NSURLSession, NSURLSessionDataTask, NSURLSessionDownloadTask) -> Void)? + var dataTaskDidReceiveData: ((NSURLSession, NSURLSessionDataTask, NSData) -> Void)? + var dataTaskWillCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse?)? + + // MARK: Delegate Methods + + func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + didReceiveResponse response: NSURLResponse, + completionHandler: (NSURLSessionResponseDisposition -> Void)) + { + var disposition: NSURLSessionResponseDisposition = .Allow + + expectedContentLength = response.expectedContentLength + + if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { + disposition = dataTaskDidReceiveResponse(session, dataTask, response) + } + + completionHandler(disposition) + } + + func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) + { + dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask) + } + + func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + if let dataTaskDidReceiveData = dataTaskDidReceiveData { + dataTaskDidReceiveData(session, dataTask, data) + } else { + if let dataStream = dataStream { + dataStream(data: data) + } else { + mutableData.appendData(data) + } + + totalBytesReceived += data.length + let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown + + progress.totalUnitCount = totalBytesExpected + progress.completedUnitCount = totalBytesReceived + + dataProgress?( + bytesReceived: Int64(data.length), + totalBytesReceived: totalBytesReceived, + totalBytesExpectedToReceive: totalBytesExpected + ) + } + } + + func URLSession( + session: NSURLSession, + dataTask: NSURLSessionDataTask, + willCacheResponse proposedResponse: NSCachedURLResponse, + completionHandler: ((NSCachedURLResponse?) -> Void)) + { + var cachedResponse: NSCachedURLResponse? = proposedResponse + + if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { + cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse) + } + + completionHandler(cachedResponse) + } + } +} + +// MARK: - CustomStringConvertible + +extension Request: CustomStringConvertible { + + /** + The textual representation used when written to an output stream, which includes the HTTP method and URL, as + well as the response status code if a response has been received. + */ + public var description: String { + var components: [String] = [] + + if let HTTPMethod = request?.HTTPMethod { + components.append(HTTPMethod) + } + + if let URLString = request?.URL?.absoluteString { + components.append(URLString) + } + + if let response = response { + components.append("(\(response.statusCode))") + } + + return components.joinWithSeparator(" ") + } +} + +// MARK: - CustomDebugStringConvertible + +extension Request: CustomDebugStringConvertible { + func cURLRepresentation() -> String { + var components = ["$ curl -i"] + + guard let request = self.request else { + return "$ curl command could not be created" + } + + let URL = request.URL + + if let HTTPMethod = request.HTTPMethod where HTTPMethod != "GET" { + components.append("-X \(HTTPMethod)") + } + + if let credentialStorage = self.session.configuration.URLCredentialStorage { + let protectionSpace = NSURLProtectionSpace( + host: URL!.host!, + port: URL!.port?.integerValue ?? 0, + `protocol`: URL!.scheme, + realm: URL!.host!, + authenticationMethod: NSURLAuthenticationMethodHTTPBasic + ) + + if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values { + for credential in credentials { + components.append("-u \(credential.user!):\(credential.password!)") + } + } else { + if let credential = delegate.credential { + components.append("-u \(credential.user!):\(credential.password!)") + } + } + } + + if session.configuration.HTTPShouldSetCookies { + if let + cookieStorage = session.configuration.HTTPCookieStorage, + cookies = cookieStorage.cookiesForURL(URL!) where !cookies.isEmpty + { + let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value ?? String());" } + components.append("-b \"\(string.substringToIndex(string.endIndex.predecessor()))\"") + } + } + + if let headerFields = request.allHTTPHeaderFields { + for (field, value) in headerFields { + switch field { + case "Cookie": + continue + default: + components.append("-H \"\(field): \(value)\"") + } + } + } + + if let additionalHeaders = session.configuration.HTTPAdditionalHeaders { + for (field, value) in additionalHeaders { + switch field { + case "Cookie": + continue + default: + components.append("-H \"\(field): \(value)\"") + } + } + } + + if let + HTTPBodyData = request.HTTPBody, + HTTPBody = String(data: HTTPBodyData, encoding: NSUTF8StringEncoding) + { + let escapedBody = HTTPBody.stringByReplacingOccurrencesOfString("\"", withString: "\\\"") + components.append("-d \"\(escapedBody)\"") + } + + components.append("\"\(URL!.absoluteString)\"") + + return components.joinWithSeparator(" \\\n\t") + } + + /// The textual representation used when written to an output stream, in the form of a cURL command. + public var debugDescription: String { + return cURLRepresentation() + } +} diff --git a/Pods/Alamofire/Source/Response.swift b/Pods/Alamofire/Source/Response.swift new file mode 100644 index 0000000..834f8ea --- /dev/null +++ b/Pods/Alamofire/Source/Response.swift @@ -0,0 +1,83 @@ +// Response.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Used to store all response data returned from a completed `Request`. +public struct Response { + /// The URL request sent to the server. + public let request: NSURLRequest? + + /// The server's response to the URL request. + public let response: NSHTTPURLResponse? + + /// The data returned by the server. + public let data: NSData? + + /// The result of response serialization. + public let result: Result + + /** + Initializes the `Response` instance with the specified URL request, URL response, server data and response + serialization result. + + - parameter request: The URL request sent to the server. + - parameter response: The server's response to the URL request. + - parameter data: The data returned by the server. + - parameter result: The result of response serialization. + + - returns: the new `Response` instance. + */ + public init(request: NSURLRequest?, response: NSHTTPURLResponse?, data: NSData?, result: Result) { + self.request = request + self.response = response + self.data = data + self.result = result + } +} + +// MARK: - CustomStringConvertible + +extension Response: CustomStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + return result.debugDescription + } +} + +// MARK: - CustomDebugStringConvertible + +extension Response: CustomDebugStringConvertible { + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the server data and the response serialization result. + public var debugDescription: String { + var output: [String] = [] + + output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil") + output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil") + output.append("[Data]: \(data?.length ?? 0) bytes") + output.append("[Result]: \(result.debugDescription)") + + return output.joinWithSeparator("\n") + } +} diff --git a/Pods/Alamofire/Source/ResponseSerialization.swift b/Pods/Alamofire/Source/ResponseSerialization.swift new file mode 100644 index 0000000..e621ece --- /dev/null +++ b/Pods/Alamofire/Source/ResponseSerialization.swift @@ -0,0 +1,355 @@ +// ResponseSerialization.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +// MARK: ResponseSerializer + +/** + The type in which all response serializers must conform to in order to serialize a response. +*/ +public protocol ResponseSerializerType { + /// The type of serialized object to be created by this `ResponseSerializerType`. + typealias SerializedObject + + /// The type of error to be created by this `ResponseSerializer` if serialization fails. + typealias ErrorObject: ErrorType + + /** + A closure used by response handlers that takes a request, response, data and error and returns a result. + */ + var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result { get } +} + +// MARK: - + +/** + A generic `ResponseSerializerType` used to serialize a request, response, and data into a serialized object. +*/ +public struct ResponseSerializer: ResponseSerializerType { + /// The type of serialized object to be created by this `ResponseSerializer`. + public typealias SerializedObject = Value + + /// The type of error to be created by this `ResponseSerializer` if serialization fails. + public typealias ErrorObject = Error + + /** + A closure used by response handlers that takes a request, response, data and error and returns a result. + */ + public var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result + + /** + Initializes the `ResponseSerializer` instance with the given serialize response closure. + + - parameter serializeResponse: The closure used to serialize the response. + + - returns: The new generic response serializer instance. + */ + public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result) { + self.serializeResponse = serializeResponse + } +} + +// MARK: - Default + +extension Request { + + /** + Adds a handler to be called once the request has finished. + + - parameter queue: The queue on which the completion handler is dispatched. + - parameter completionHandler: The code to be executed once the request has finished. + + - returns: The request. + */ + public func response( + queue queue: dispatch_queue_t? = nil, + completionHandler: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Void) + -> Self + { + delegate.queue.addOperationWithBlock { + dispatch_async(queue ?? dispatch_get_main_queue()) { + completionHandler(self.request, self.response, self.delegate.data, self.delegate.error) + } + } + + return self + } + + /** + Adds a handler to be called once the request has finished. + + - parameter queue: The queue on which the completion handler is dispatched. + - parameter responseSerializer: The response serializer responsible for serializing the request, response, + and data. + - parameter completionHandler: The code to be executed once the request has finished. + + - returns: The request. + */ + public func response( + queue queue: dispatch_queue_t? = nil, + responseSerializer: T, + completionHandler: Response -> Void) + -> Self + { + delegate.queue.addOperationWithBlock { + let result = responseSerializer.serializeResponse( + self.request, + self.response, + self.delegate.data, + self.delegate.error + ) + + dispatch_async(queue ?? dispatch_get_main_queue()) { + let response = Response( + request: self.request, + response: self.response, + data: self.delegate.data, + result: result + ) + + completionHandler(response) + } + } + + return self + } +} + +// MARK: - Data + +extension Request { + + /** + Creates a response serializer that returns the associated data as-is. + + - returns: A data response serializer. + */ + public static func dataResponseSerializer() -> ResponseSerializer { + return ResponseSerializer { _, response, data, error in + guard error == nil else { return .Failure(error!) } + + if let response = response where response.statusCode == 204 { return .Success(NSData()) } + + guard let validData = data else { + let failureReason = "Data could not be serialized. Input data was nil." + let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + + return .Success(validData) + } + } + + /** + Adds a handler to be called once the request has finished. + + - parameter completionHandler: The code to be executed once the request has finished. + + - returns: The request. + */ + public func responseData(completionHandler: Response -> Void) -> Self { + return response(responseSerializer: Request.dataResponseSerializer(), completionHandler: completionHandler) + } +} + +// MARK: - String + +extension Request { + + /** + Creates a response serializer that returns a string initialized from the response data with the specified + string encoding. + + - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server + response, falling back to the default HTTP default character set, ISO-8859-1. + + - returns: A string response serializer. + */ + public static func stringResponseSerializer( + var encoding encoding: NSStringEncoding? = nil) + -> ResponseSerializer + { + return ResponseSerializer { _, response, data, error in + guard error == nil else { return .Failure(error!) } + + if let response = response where response.statusCode == 204 { return .Success("") } + + guard let validData = data else { + let failureReason = "String could not be serialized. Input data was nil." + let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + + if let encodingName = response?.textEncodingName where encoding == nil { + encoding = CFStringConvertEncodingToNSStringEncoding( + CFStringConvertIANACharSetNameToEncoding(encodingName) + ) + } + + let actualEncoding = encoding ?? NSISOLatin1StringEncoding + + if let string = String(data: validData, encoding: actualEncoding) { + return .Success(string) + } else { + let failureReason = "String could not be serialized with encoding: \(actualEncoding)" + let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + } + } + + /** + Adds a handler to be called once the request has finished. + + - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the + server response, falling back to the default HTTP default character set, + ISO-8859-1. + - parameter completionHandler: A closure to be executed once the request has finished. + + - returns: The request. + */ + public func responseString( + encoding encoding: NSStringEncoding? = nil, + completionHandler: Response -> Void) + -> Self + { + return response( + responseSerializer: Request.stringResponseSerializer(encoding: encoding), + completionHandler: completionHandler + ) + } +} + +// MARK: - JSON + +extension Request { + + /** + Creates a response serializer that returns a JSON object constructed from the response data using + `NSJSONSerialization` with the specified reading options. + + - parameter options: The JSON serialization reading options. `.AllowFragments` by default. + + - returns: A JSON object response serializer. + */ + public static func JSONResponseSerializer( + options options: NSJSONReadingOptions = .AllowFragments) + -> ResponseSerializer + { + return ResponseSerializer { _, response, data, error in + guard error == nil else { return .Failure(error!) } + + if let response = response where response.statusCode == 204 { return .Success(NSNull()) } + + guard let validData = data where validData.length > 0 else { + let failureReason = "JSON could not be serialized. Input data was nil or zero length." + let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + + do { + let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options) + return .Success(JSON) + } catch { + return .Failure(error as NSError) + } + } + } + + /** + Adds a handler to be called once the request has finished. + + - parameter options: The JSON serialization reading options. `.AllowFragments` by default. + - parameter completionHandler: A closure to be executed once the request has finished. + + - returns: The request. + */ + public func responseJSON( + options options: NSJSONReadingOptions = .AllowFragments, + completionHandler: Response -> Void) + -> Self + { + return response( + responseSerializer: Request.JSONResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} + +// MARK: - Property List + +extension Request { + + /** + Creates a response serializer that returns an object constructed from the response data using + `NSPropertyListSerialization` with the specified reading options. + + - parameter options: The property list reading options. `NSPropertyListReadOptions()` by default. + + - returns: A property list object response serializer. + */ + public static func propertyListResponseSerializer( + options options: NSPropertyListReadOptions = NSPropertyListReadOptions()) + -> ResponseSerializer + { + return ResponseSerializer { _, response, data, error in + guard error == nil else { return .Failure(error!) } + + if let response = response where response.statusCode == 204 { return .Success(NSNull()) } + + guard let validData = data where validData.length > 0 else { + let failureReason = "Property list could not be serialized. Input data was nil or zero length." + let error = Error.errorWithCode(.PropertyListSerializationFailed, failureReason: failureReason) + return .Failure(error) + } + + do { + let plist = try NSPropertyListSerialization.propertyListWithData(validData, options: options, format: nil) + return .Success(plist) + } catch { + return .Failure(error as NSError) + } + } + } + + /** + Adds a handler to be called once the request has finished. + + - parameter options: The property list reading options. `0` by default. + - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 3 + arguments: the URL request, the URL response, the server data and the result + produced while creating the property list. + + - returns: The request. + */ + public func responsePropertyList( + options options: NSPropertyListReadOptions = NSPropertyListReadOptions(), + completionHandler: Response -> Void) + -> Self + { + return response( + responseSerializer: Request.propertyListResponseSerializer(options: options), + completionHandler: completionHandler + ) + } +} diff --git a/Pods/Alamofire/Source/Result.swift b/Pods/Alamofire/Source/Result.swift new file mode 100644 index 0000000..4aeeab6 --- /dev/null +++ b/Pods/Alamofire/Source/Result.swift @@ -0,0 +1,101 @@ +// Result.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + Used to represent whether a request was successful or encountered an error. + + - Success: The request and all post processing operations were successful resulting in the serialization of the + provided associated value. + - Failure: The request encountered an error resulting in a failure. The associated values are the original data + provided by the server as well as the error that caused the failure. +*/ +public enum Result { + case Success(Value) + case Failure(Error) + + /// Returns `true` if the result is a success, `false` otherwise. + public var isSuccess: Bool { + switch self { + case .Success: + return true + case .Failure: + return false + } + } + + /// Returns `true` if the result is a failure, `false` otherwise. + public var isFailure: Bool { + return !isSuccess + } + + /// Returns the associated value if the result is a success, `nil` otherwise. + public var value: Value? { + switch self { + case .Success(let value): + return value + case .Failure: + return nil + } + } + + /// Returns the associated error value if the result is a failure, `nil` otherwise. + public var error: Error? { + switch self { + case .Success: + return nil + case .Failure(let error): + return error + } + } +} + +// MARK: - CustomStringConvertible + +extension Result: CustomStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + switch self { + case .Success: + return "SUCCESS" + case .Failure: + return "FAILURE" + } + } +} + +// MARK: - CustomDebugStringConvertible + +extension Result: CustomDebugStringConvertible { + /// The debug textual representation used when written to an output stream, which includes whether the result was a + /// success or failure in addition to the value or error. + public var debugDescription: String { + switch self { + case .Success(let value): + return "SUCCESS: \(value)" + case .Failure(let error): + return "FAILURE: \(error)" + } + } +} diff --git a/Pods/Alamofire/Source/ServerTrustPolicy.swift b/Pods/Alamofire/Source/ServerTrustPolicy.swift new file mode 100644 index 0000000..b6efbce --- /dev/null +++ b/Pods/Alamofire/Source/ServerTrustPolicy.swift @@ -0,0 +1,305 @@ +// ServerTrustPolicy.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host. +public class ServerTrustPolicyManager { + /// The dictionary of policies mapped to a particular host. + public let policies: [String: ServerTrustPolicy] + + /** + Initializes the `ServerTrustPolicyManager` instance with the given policies. + + Since different servers and web services can have different leaf certificates, intermediate and even root + certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This + allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key + pinning for host3 and disabling evaluation for host4. + + - parameter policies: A dictionary of all policies mapped to a particular host. + + - returns: The new `ServerTrustPolicyManager` instance. + */ + public init(policies: [String: ServerTrustPolicy]) { + self.policies = policies + } + + /** + Returns the `ServerTrustPolicy` for the given host if applicable. + + By default, this method will return the policy that perfectly matches the given host. Subclasses could override + this method and implement more complex mapping implementations such as wildcards. + + - parameter host: The host to use when searching for a matching policy. + + - returns: The server trust policy for the given host if found. + */ + public func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? { + return policies[host] + } +} + +// MARK: - + +extension NSURLSession { + private struct AssociatedKeys { + static var ManagerKey = "NSURLSession.ServerTrustPolicyManager" + } + + var serverTrustPolicyManager: ServerTrustPolicyManager? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.ManagerKey) as? ServerTrustPolicyManager + } + set (manager) { + objc_setAssociatedObject(self, &AssociatedKeys.ManagerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + +// MARK: - ServerTrustPolicy + +/** + The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when + connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust + with a given set of criteria to determine whether the server trust is valid and the connection should be made. + + Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other + vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged + to route all communication over an HTTPS connection with pinning enabled. + + - PerformDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to + validate the host provided by the challenge. Applications are encouraged to always + validate the host in production environments to guarantee the validity of the server's + certificate chain. + + - PinCertificates: Uses the pinned certificates to validate the server trust. The server trust is + considered valid if one of the pinned certificates match one of the server certificates. + By validating both the certificate chain and host, certificate pinning provides a very + secure form of server trust validation mitigating most, if not all, MITM attacks. + Applications are encouraged to always validate the host and require a valid certificate + chain in production environments. + + - PinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered + valid if one of the pinned public keys match one of the server certificate public keys. + By validating both the certificate chain and host, public key pinning provides a very + secure form of server trust validation mitigating most, if not all, MITM attacks. + Applications are encouraged to always validate the host and require a valid certificate + chain in production environments. + + - DisableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid. + + - CustomEvaluation: Uses the associated closure to evaluate the validity of the server trust. +*/ +public enum ServerTrustPolicy { + case PerformDefaultEvaluation(validateHost: Bool) + case PinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool) + case PinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool) + case DisableEvaluation + case CustomEvaluation((serverTrust: SecTrust, host: String) -> Bool) + + // MARK: - Bundle Location + + /** + Returns all certificates within the given bundle with a `.cer` file extension. + + - parameter bundle: The bundle to search for all `.cer` files. + + - returns: All certificates within the given bundle. + */ + public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] { + var certificates: [SecCertificate] = [] + + for path in bundle.pathsForResourcesOfType(".cer", inDirectory: nil) { + if let + certificateData = NSData(contentsOfFile: path), + certificate = SecCertificateCreateWithData(nil, certificateData) + { + certificates.append(certificate) + } + } + + return certificates + } + + /** + Returns all public keys within the given bundle with a `.cer` file extension. + + - parameter bundle: The bundle to search for all `*.cer` files. + + - returns: All public keys within the given bundle. + */ + public static func publicKeysInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecKey] { + var publicKeys: [SecKey] = [] + + for certificate in certificatesInBundle(bundle) { + if let publicKey = publicKeyForCertificate(certificate) { + publicKeys.append(publicKey) + } + } + + return publicKeys + } + + // MARK: - Evaluation + + /** + Evaluates whether the server trust is valid for the given host. + + - parameter serverTrust: The server trust to evaluate. + - parameter host: The host of the challenge protection space. + + - returns: Whether the server trust is valid. + */ + public func evaluateServerTrust(serverTrust: SecTrust, isValidForHost host: String) -> Bool { + var serverTrustIsValid = false + + switch self { + case let .PerformDefaultEvaluation(validateHost): + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, [policy]) + + serverTrustIsValid = trustIsValid(serverTrust) + case let .PinCertificates(pinnedCertificates, validateCertificateChain, validateHost): + if validateCertificateChain { + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, [policy]) + + SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates) + SecTrustSetAnchorCertificatesOnly(serverTrust, true) + + serverTrustIsValid = trustIsValid(serverTrust) + } else { + let serverCertificatesDataArray = certificateDataForTrust(serverTrust) + + //====================================================================================================== + // The following `[] +` is a temporary workaround for a Swift 2.0 compiler error. This workaround should + // be removed once the following radar has been resolved: + // - http://openradar.appspot.com/radar?id=6082025006039040 + //====================================================================================================== + + let pinnedCertificatesDataArray = certificateDataForCertificates([] + pinnedCertificates) + + outerLoop: for serverCertificateData in serverCertificatesDataArray { + for pinnedCertificateData in pinnedCertificatesDataArray { + if serverCertificateData.isEqualToData(pinnedCertificateData) { + serverTrustIsValid = true + break outerLoop + } + } + } + } + case let .PinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost): + var certificateChainEvaluationPassed = true + + if validateCertificateChain { + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, [policy]) + + certificateChainEvaluationPassed = trustIsValid(serverTrust) + } + + if certificateChainEvaluationPassed { + outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeysForTrust(serverTrust) as [AnyObject] { + for pinnedPublicKey in pinnedPublicKeys as [AnyObject] { + if serverPublicKey.isEqual(pinnedPublicKey) { + serverTrustIsValid = true + break outerLoop + } + } + } + } + case .DisableEvaluation: + serverTrustIsValid = true + case let .CustomEvaluation(closure): + serverTrustIsValid = closure(serverTrust: serverTrust, host: host) + } + + return serverTrustIsValid + } + + // MARK: - Private - Trust Validation + + private func trustIsValid(trust: SecTrust) -> Bool { + var isValid = false + + var result = SecTrustResultType(kSecTrustResultInvalid) + let status = SecTrustEvaluate(trust, &result) + + if status == errSecSuccess { + let unspecified = SecTrustResultType(kSecTrustResultUnspecified) + let proceed = SecTrustResultType(kSecTrustResultProceed) + + isValid = result == unspecified || result == proceed + } + + return isValid + } + + // MARK: - Private - Certificate Data + + private func certificateDataForTrust(trust: SecTrust) -> [NSData] { + var certificates: [SecCertificate] = [] + + for index in 0.. [NSData] { + return certificates.map { SecCertificateCopyData($0) as NSData } + } + + // MARK: - Private - Public Key Extraction + + private static func publicKeysForTrust(trust: SecTrust) -> [SecKey] { + var publicKeys: [SecKey] = [] + + for index in 0.. SecKey? { + var publicKey: SecKey? + + let policy = SecPolicyCreateBasicX509() + var trust: SecTrust? + let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust) + + if let trust = trust where trustCreationStatus == errSecSuccess { + publicKey = SecTrustCopyPublicKey(trust) + } + + return publicKey + } +} diff --git a/Pods/Alamofire/Source/Stream.swift b/Pods/Alamofire/Source/Stream.swift new file mode 100644 index 0000000..af89d5d --- /dev/null +++ b/Pods/Alamofire/Source/Stream.swift @@ -0,0 +1,180 @@ +// Stream.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +#if !os(watchOS) + +@available(iOS 9.0, OSX 10.11, *) +extension Manager { + private enum Streamable { + case Stream(String, Int) + case NetService(NSNetService) + } + + private func stream(streamable: Streamable) -> Request { + var streamTask: NSURLSessionStreamTask! + + switch streamable { + case .Stream(let hostName, let port): + dispatch_sync(queue) { + streamTask = self.session.streamTaskWithHostName(hostName, port: port) + } + case .NetService(let netService): + dispatch_sync(queue) { + streamTask = self.session.streamTaskWithNetService(netService) + } + } + + let request = Request(session: session, task: streamTask) + + delegate[request.delegate.task] = request.delegate + + if startRequestsImmediately { + request.resume() + } + + return request + } + + /** + Creates a request for bidirectional streaming with the given hostname and port. + + - parameter hostName: The hostname of the server to connect to. + - parameter port: The port of the server to connect to. + + :returns: The created stream request. + */ + public func stream(hostName hostName: String, port: Int) -> Request { + return stream(.Stream(hostName, port)) + } + + /** + Creates a request for bidirectional streaming with the given `NSNetService`. + + - parameter netService: The net service used to identify the endpoint. + + - returns: The created stream request. + */ + public func stream(netService netService: NSNetService) -> Request { + return stream(.NetService(netService)) + } +} + +// MARK: - + +@available(iOS 9.0, OSX 10.11, *) +extension Manager.SessionDelegate: NSURLSessionStreamDelegate { + + // MARK: Override Closures + + /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:readClosedForStreamTask:`. + public var streamTaskReadClosed: ((NSURLSession, NSURLSessionStreamTask) -> Void)? { + get { + return _streamTaskReadClosed as? (NSURLSession, NSURLSessionStreamTask) -> Void + } + set { + _streamTaskReadClosed = newValue + } + } + + /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:writeClosedForStreamTask:`. + public var streamTaskWriteClosed: ((NSURLSession, NSURLSessionStreamTask) -> Void)? { + get { + return _streamTaskWriteClosed as? (NSURLSession, NSURLSessionStreamTask) -> Void + } + set { + _streamTaskWriteClosed = newValue + } + } + + /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:betterRouteDiscoveredForStreamTask:`. + public var streamTaskBetterRouteDiscovered: ((NSURLSession, NSURLSessionStreamTask) -> Void)? { + get { + return _streamTaskBetterRouteDiscovered as? (NSURLSession, NSURLSessionStreamTask) -> Void + } + set { + _streamTaskBetterRouteDiscovered = newValue + } + } + + /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:streamTask:didBecomeInputStream:outputStream:`. + public var streamTaskDidBecomeInputStream: ((NSURLSession, NSURLSessionStreamTask, NSInputStream, NSOutputStream) -> Void)? { + get { + return _streamTaskDidBecomeInputStream as? (NSURLSession, NSURLSessionStreamTask, NSInputStream, NSOutputStream) -> Void + } + set { + _streamTaskDidBecomeInputStream = newValue + } + } + + // MARK: Delegate Methods + + /** + Tells the delegate that the read side of the connection has been closed. + + - parameter session: The session. + - parameter streamTask: The stream task. + */ + public func URLSession(session: NSURLSession, readClosedForStreamTask streamTask: NSURLSessionStreamTask) { + streamTaskReadClosed?(session, streamTask) + } + + /** + Tells the delegate that the write side of the connection has been closed. + + - parameter session: The session. + - parameter streamTask: The stream task. + */ + public func URLSession(session: NSURLSession, writeClosedForStreamTask streamTask: NSURLSessionStreamTask) { + streamTaskWriteClosed?(session, streamTask) + } + + /** + Tells the delegate that the system has determined that a better route to the host is available. + + - parameter session: The session. + - parameter streamTask: The stream task. + */ + public func URLSession(session: NSURLSession, betterRouteDiscoveredForStreamTask streamTask: NSURLSessionStreamTask) { + streamTaskBetterRouteDiscovered?(session, streamTask) + } + + /** + Tells the delegate that the stream task has been completed and provides the unopened stream objects. + + - parameter session: The session. + - parameter streamTask: The stream task. + - parameter inputStream: The new input stream. + - parameter outputStream: The new output stream. + */ + public func URLSession( + session: NSURLSession, + streamTask: NSURLSessionStreamTask, + didBecomeInputStream inputStream: NSInputStream, + outputStream: NSOutputStream) + { + streamTaskDidBecomeInputStream?(session, streamTask, inputStream, outputStream) + } +} + +#endif diff --git a/Pods/Alamofire/Source/Upload.swift b/Pods/Alamofire/Source/Upload.swift new file mode 100644 index 0000000..f7cc8bc --- /dev/null +++ b/Pods/Alamofire/Source/Upload.swift @@ -0,0 +1,372 @@ +// Upload.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +extension Manager { + private enum Uploadable { + case Data(NSURLRequest, NSData) + case File(NSURLRequest, NSURL) + case Stream(NSURLRequest, NSInputStream) + } + + private func upload(uploadable: Uploadable) -> Request { + var uploadTask: NSURLSessionUploadTask! + var HTTPBodyStream: NSInputStream? + + switch uploadable { + case .Data(let request, let data): + dispatch_sync(queue) { + uploadTask = self.session.uploadTaskWithRequest(request, fromData: data) + } + case .File(let request, let fileURL): + dispatch_sync(queue) { + uploadTask = self.session.uploadTaskWithRequest(request, fromFile: fileURL) + } + case .Stream(let request, let stream): + dispatch_sync(queue) { + uploadTask = self.session.uploadTaskWithStreamedRequest(request) + } + + HTTPBodyStream = stream + } + + let request = Request(session: session, task: uploadTask) + + if HTTPBodyStream != nil { + request.delegate.taskNeedNewBodyStream = { _, _ in + return HTTPBodyStream + } + } + + delegate[request.delegate.task] = request.delegate + + if startRequestsImmediately { + request.resume() + } + + return request + } + + // MARK: File + + /** + Creates a request for uploading a file to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request + - parameter file: The file to upload + + - returns: The created upload request. + */ + public func upload(URLRequest: URLRequestConvertible, file: NSURL) -> Request { + return upload(.File(URLRequest.URLRequest, file)) + } + + /** + Creates a request for uploading a file to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter file: The file to upload + + - returns: The created upload request. + */ + public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + file: NSURL) + -> Request + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + return upload(mutableURLRequest, file: file) + } + + // MARK: Data + + /** + Creates a request for uploading data to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request. + - parameter data: The data to upload. + + - returns: The created upload request. + */ + public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request { + return upload(.Data(URLRequest.URLRequest, data)) + } + + /** + Creates a request for uploading data to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter data: The data to upload + + - returns: The created upload request. + */ + public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + data: NSData) + -> Request + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + + return upload(mutableURLRequest, data: data) + } + + // MARK: Stream + + /** + Creates a request for uploading a stream to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request. + - parameter stream: The stream to upload. + + - returns: The created upload request. + */ + public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request { + return upload(.Stream(URLRequest.URLRequest, stream)) + } + + /** + Creates a request for uploading a stream to the specified URL request. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter stream: The stream to upload. + + - returns: The created upload request. + */ + public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + stream: NSInputStream) + -> Request + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + + return upload(mutableURLRequest, stream: stream) + } + + // MARK: MultipartFormData + + /// Default memory threshold used when encoding `MultipartFormData`. + public static let MultipartFormDataEncodingMemoryThreshold: UInt64 = 10 * 1024 * 1024 + + /** + Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as + associated values. + + - Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with + streaming information. + - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding + error. + */ + public enum MultipartFormDataEncodingResult { + case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?) + case Failure(ErrorType) + } + + /** + Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request. + + It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative + payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + used for larger payloads such as video content. + + The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + technique was used. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter method: The HTTP method. + - parameter URLString: The URL string. + - parameter headers: The HTTP headers. `nil` by default. + - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + `MultipartFormDataEncodingMemoryThreshold` by default. + - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. + */ + public func upload( + method: Method, + _ URLString: URLStringConvertible, + headers: [String: String]? = nil, + multipartFormData: MultipartFormData -> Void, + encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold, + encodingCompletion: (MultipartFormDataEncodingResult -> Void)?) + { + let mutableURLRequest = URLRequest(method, URLString, headers: headers) + + return upload( + mutableURLRequest, + multipartFormData: multipartFormData, + encodingMemoryThreshold: encodingMemoryThreshold, + encodingCompletion: encodingCompletion + ) + } + + /** + Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request. + + It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative + payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + used for larger payloads such as video content. + + The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + technique was used. + + If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. + + - parameter URLRequest: The URL request. + - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. + - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. + `MultipartFormDataEncodingMemoryThreshold` by default. + - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. + */ + public func upload( + URLRequest: URLRequestConvertible, + multipartFormData: MultipartFormData -> Void, + encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold, + encodingCompletion: (MultipartFormDataEncodingResult -> Void)?) + { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { + let formData = MultipartFormData() + multipartFormData(formData) + + let URLRequestWithContentType = URLRequest.URLRequest + URLRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type") + + let isBackgroundSession = self.session.configuration.identifier != nil + + if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession { + do { + let data = try formData.encode() + let encodingResult = MultipartFormDataEncodingResult.Success( + request: self.upload(URLRequestWithContentType, data: data), + streamingFromDisk: false, + streamFileURL: nil + ) + + dispatch_async(dispatch_get_main_queue()) { + encodingCompletion?(encodingResult) + } + } catch { + dispatch_async(dispatch_get_main_queue()) { + encodingCompletion?(.Failure(error as NSError)) + } + } + } else { + let fileManager = NSFileManager.defaultManager() + let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory()) + let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.manager/multipart.form.data") + let fileName = NSUUID().UUIDString + let fileURL = directoryURL.URLByAppendingPathComponent(fileName) + + do { + try fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil) + try formData.writeEncodedDataToDisk(fileURL) + + dispatch_async(dispatch_get_main_queue()) { + let encodingResult = MultipartFormDataEncodingResult.Success( + request: self.upload(URLRequestWithContentType, file: fileURL), + streamingFromDisk: true, + streamFileURL: fileURL + ) + encodingCompletion?(encodingResult) + } + } catch { + dispatch_async(dispatch_get_main_queue()) { + encodingCompletion?(.Failure(error as NSError)) + } + } + } + } + } +} + +// MARK: - + +extension Request { + + // MARK: - UploadTaskDelegate + + class UploadTaskDelegate: DataTaskDelegate { + var uploadTask: NSURLSessionUploadTask? { return task as? NSURLSessionUploadTask } + var uploadProgress: ((Int64, Int64, Int64) -> Void)! + + // MARK: - NSURLSessionTaskDelegate + + // MARK: Override Closures + + var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)? + + // MARK: Delegate Methods + + func URLSession( + session: NSURLSession, + task: NSURLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + { + if let taskDidSendBodyData = taskDidSendBodyData { + taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } else { + progress.totalUnitCount = totalBytesExpectedToSend + progress.completedUnitCount = totalBytesSent + + uploadProgress?(bytesSent, totalBytesSent, totalBytesExpectedToSend) + } + } + } +} diff --git a/Pods/Alamofire/Source/Validation.swift b/Pods/Alamofire/Source/Validation.swift new file mode 100644 index 0000000..6763917 --- /dev/null +++ b/Pods/Alamofire/Source/Validation.swift @@ -0,0 +1,189 @@ +// Validation.swift +// +// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +extension Request { + + /** + Used to represent whether validation was successful or encountered an error resulting in a failure. + + - Success: The validation was successful. + - Failure: The validation failed encountering the provided error. + */ + public enum ValidationResult { + case Success + case Failure(NSError) + } + + /** + A closure used to validate a request that takes a URL request and URL response, and returns whether the + request was valid. + */ + public typealias Validation = (NSURLRequest?, NSHTTPURLResponse) -> ValidationResult + + /** + Validates the request, using the specified closure. + + If validation fails, subsequent calls to response handlers will have an associated error. + + - parameter validation: A closure to validate the request. + + - returns: The request. + */ + public func validate(validation: Validation) -> Self { + delegate.queue.addOperationWithBlock { + if let + response = self.response where self.delegate.error == nil, + case let .Failure(error) = validation(self.request, response) + { + self.delegate.error = error + } + } + + return self + } + + // MARK: - Status Code + + /** + Validates that the response has a status code in the specified range. + + If validation fails, subsequent calls to response handlers will have an associated error. + + - parameter range: The range of acceptable status codes. + + - returns: The request. + */ + public func validate(statusCode acceptableStatusCode: S) -> Self { + return validate { _, response in + if acceptableStatusCode.contains(response.statusCode) { + return .Success + } else { + let failureReason = "Response status code was unacceptable: \(response.statusCode)" + return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason)) + } + } + } + + // MARK: - Content-Type + + private struct MIMEType { + let type: String + let subtype: String + + init?(_ string: String) { + let components: [String] = { + let stripped = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + let split = stripped.substringToIndex(stripped.rangeOfString(";")?.startIndex ?? stripped.endIndex) + return split.componentsSeparatedByString("/") + }() + + if let + type = components.first, + subtype = components.last + { + self.type = type + self.subtype = subtype + } else { + return nil + } + } + + func matches(MIME: MIMEType) -> Bool { + switch (type, subtype) { + case (MIME.type, MIME.subtype), (MIME.type, "*"), ("*", MIME.subtype), ("*", "*"): + return true + default: + return false + } + } + } + + /** + Validates that the response has a content type in the specified array. + + If validation fails, subsequent calls to response handlers will have an associated error. + + - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + + - returns: The request. + */ + public func validate(contentType acceptableContentTypes: S) -> Self { + return validate { _, response in + guard let validData = self.delegate.data where validData.length > 0 else { return .Success } + + if let + responseContentType = response.MIMEType, + responseMIMEType = MIMEType(responseContentType) + { + for contentType in acceptableContentTypes { + if let acceptableMIMEType = MIMEType(contentType) where acceptableMIMEType.matches(responseMIMEType) { + return .Success + } + } + } else { + for contentType in acceptableContentTypes { + if let MIMEType = MIMEType(contentType) where MIMEType.type == "*" && MIMEType.subtype == "*" { + return .Success + } + } + } + + let failureReason: String + + if let responseContentType = response.MIMEType { + failureReason = ( + "Response content type \"\(responseContentType)\" does not match any acceptable " + + "content types: \(acceptableContentTypes)" + ) + } else { + failureReason = "Response content type was missing and acceptable content type does not match \"*/*\"" + } + + return .Failure(Error.errorWithCode(.ContentTypeValidationFailed, failureReason: failureReason)) + } + } + + // MARK: - Automatic + + /** + Validates that the response has a status code in the default acceptable range of 200...299, and that the content + type matches any specified in the Accept HTTP header field. + + If validation fails, subsequent calls to response handlers will have an associated error. + + - returns: The request. + */ + public func validate() -> Self { + let acceptableStatusCodes: Range = 200..<300 + let acceptableContentTypes: [String] = { + if let accept = request?.valueForHTTPHeaderField("Accept") { + return accept.componentsSeparatedByString(",") + } + + return ["*/*"] + }() + + return validate(statusCode: acceptableStatusCodes).validate(contentType: acceptableContentTypes) + } +} diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock new file mode 100644 index 0000000..8f9fc0c --- /dev/null +++ b/Pods/Manifest.lock @@ -0,0 +1,13 @@ +PODS: + - Alamofire (3.1.2) + - SwiftyJSON (2.3.1) + +DEPENDENCIES: + - Alamofire (~> 3.0) + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 7c16ca65f3c7e681fd925fd7f2dec7747ff96855 + SwiftyJSON: 592b53bee5ef3dd9b3bebc6b9cb7ee35426ae8c3 + +COCOAPODS: 0.39.0 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6082563 --- /dev/null +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,712 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 03F494989CC1A8857B68A317D5D6860F /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48AB7332B62C88E1579623BFA20BF7B0 /* Response.swift */; }; + 0681ADC8BAE2C3185F13487BAAB4D9DD /* Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241D90DB9BE97B82FFB08B1946F6339 /* Upload.swift */; }; + 2C5450AC69398958CF6F7539EF7D99E5 /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 38F6AFABD8420CA898ACB691C6B8FF25 /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4AFB530721F7A70D52CE2F7733FBB799 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E4E89230EF59BC255123B67864ACF77 /* Foundation.framework */; }; + 4DE5FCC41D100B113B6645EA64410F16 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EEA8AA3C5CCFDDFA31E722F8210645A /* ParameterEncoding.swift */; }; + 5716F2949A097F0AE0748CF27E2D2CAF /* SwiftyJSON-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F5B931D671EF0284A9FF39CFD89449 /* SwiftyJSON-dummy.m */; }; + 5A5D9C98B5985F6C030B1792AE9D553F /* Pods-Anyway-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6C3BEF7CF735F64C406CEE2FE2170E /* Pods-Anyway-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 66AC9ABC2B5BA5A8413C8FA8BCF6F2E5 /* Pods-Anyway-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = F5203DAF180A9A8801705F411B711B01 /* Pods-Anyway-dummy.m */; }; + 80F496237530D382A045A29654D8C11C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119593604F171C523758B40F13989E56 /* ServerTrustPolicy.swift */; }; + 82971968CBDAB224212EEB4607C9FB8D /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803AFFFB74C7FCB5BB03AD0CC971D410 /* Result.swift */; }; + 8399DBEE3E2D98EB1F466132E476F4D9 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D38096C0BF680EE3C06CE2ED8DE0284 /* MultipartFormData.swift */; }; + 96D99D0C2472535A169DED65CB231CD7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E4E89230EF59BC255123B67864ACF77 /* Foundation.framework */; }; + A2C172FE407C0BC3478ADCA91A6C9CEC /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31225DFA1BEE8243D0A545D351C664A4 /* Manager.swift */; }; + A3505FA2FB3067D53847AD288AC04F03 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCA2890C00BEE64B4DB90115D4790F5 /* Download.swift */; }; + B0FB4B01682814B9E3D32F9DC4A5E762 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC138B2009D0806DE14B1B07A8C3D06B /* Alamofire.swift */; }; + B6D2DC3E3DA44CD382B9B425F40E11C1 /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 929F928737A2DD10C19B5272376CF1D4 /* Alamofire-dummy.m */; }; + C6B4D108A428C3A7AD2704799BA3735A /* SwiftyJSON-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5005C61ED0B8DE99B71D469F0B33C0CF /* SwiftyJSON-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C75519F0450166A6F28126ECC7664E9C /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DCC27A63156824B4B3D7258B79F3CA /* Validation.swift */; }; + D21B7325B3642887BFBE977E021F2D26 /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33E70390B0FF97AE9E4C1834B5A57F68 /* ResponseSerialization.swift */; }; + D5A76944FC77C05BE91089C0DA494C1B /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F6028763A0FDEC1A4336E309A8F8E5C /* SwiftyJSON.swift */; }; + D75CA395D510E08C404E55F5BDAE55CE /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8ECF00A31C551E81F4AFAE58AE28DE /* Error.swift */; }; + F4DB2CCDD9C53C828DDA55136C4D2984 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E4E89230EF59BC255123B67864ACF77 /* Foundation.framework */; }; + FC14480CECE872865A9C6E584F886DA3 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B92EB57AB62D49F40AAD98AE5B7BC0 /* Request.swift */; }; + FEF0D7653948988B804226129471C1EC /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824A2DB65E4419058AC364755541F4A1 /* Stream.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00A74EC356DE13C2EBC455DE489B3336 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6DE59327662671F8AEDB2C1C95256F9D; + remoteInfo = SwiftyJSON; + }; + BFFC33DFF32EAD3077930D1A99B8FB27 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432ECC54282C84882B482CCB4CF227FC; + remoteInfo = Alamofire; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 01F5B931D671EF0284A9FF39CFD89449 /* SwiftyJSON-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftyJSON-dummy.m"; sourceTree = ""; }; + 0B5EE877B751F23E44E957B559809840 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0D9B065AEB32C6D6C9C3C9D3815DDAE4 /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; + 0F8ECF00A31C551E81F4AFAE58AE28DE /* Error.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Error.swift; path = Source/Error.swift; sourceTree = ""; }; + 119593604F171C523758B40F13989E56 /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustPolicy.swift; path = Source/ServerTrustPolicy.swift; sourceTree = ""; }; + 1241D90DB9BE97B82FFB08B1946F6339 /* Upload.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Upload.swift; path = Source/Upload.swift; sourceTree = ""; }; + 168DD39527971FC3D53B64CEFBBCE997 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 222C4BF8B3694CDFD93F89C630851F89 /* Pods-Anyway.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-Anyway.modulemap"; sourceTree = ""; }; + 228E0F0E166A1E539381EFAB03E354B9 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = Alamofire.modulemap; sourceTree = ""; }; + 26B92EB57AB62D49F40AAD98AE5B7BC0 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = ""; }; + 27D9D9A0B31ECE9B436F57B6B414091C /* Pods-Anyway-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Anyway-acknowledgements.plist"; sourceTree = ""; }; + 31225DFA1BEE8243D0A545D351C664A4 /* Manager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Manager.swift; path = Source/Manager.swift; sourceTree = ""; }; + 33E70390B0FF97AE9E4C1834B5A57F68 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/ResponseSerialization.swift; sourceTree = ""; }; + 36F10C5F4DF603A640699C63C7474C9A /* SwiftyJSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 38F6AFABD8420CA898ACB691C6B8FF25 /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; + 392E165A73DB131921E6A845CE94EC4D /* Alamofire.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.xcconfig; sourceTree = ""; }; + 3E4E89230EF59BC255123B67864ACF77 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 46E6854A887E54294337DE1E1DC716B1 /* Pods-Anyway-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Anyway-acknowledgements.markdown"; sourceTree = ""; }; + 48AB7332B62C88E1579623BFA20BF7B0 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Response.swift; sourceTree = ""; }; + 5005C61ED0B8DE99B71D469F0B33C0CF /* SwiftyJSON-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-umbrella.h"; sourceTree = ""; }; + 59BBFF8F921F0F0084872CF6561D9114 /* SwiftyJSON-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-prefix.pch"; sourceTree = ""; }; + 5A974C2BACAA1BC8BD9AB9085A77C421 /* Pods-Anyway.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Anyway.debug.xcconfig"; sourceTree = ""; }; + 5F6028763A0FDEC1A4336E309A8F8E5C /* SwiftyJSON.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftyJSON.swift; path = Source/SwiftyJSON.swift; sourceTree = ""; }; + 64307F8CAA61F9E4AFAEBB14B183158A /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7D38096C0BF680EE3C06CE2ED8DE0284 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/MultipartFormData.swift; sourceTree = ""; }; + 803AFFFB74C7FCB5BB03AD0CC971D410 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Source/Result.swift; sourceTree = ""; }; + 824A2DB65E4419058AC364755541F4A1 /* Stream.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Stream.swift; path = Source/Stream.swift; sourceTree = ""; }; + 83CFE890CC09E0FC5F3802F9E36459B5 /* Pods_Anyway.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Anyway.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8EEA8AA3C5CCFDDFA31E722F8210645A /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/ParameterEncoding.swift; sourceTree = ""; }; + 929F928737A2DD10C19B5272376CF1D4 /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; + 99DCC27A63156824B4B3D7258B79F3CA /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Validation.swift; sourceTree = ""; }; + 9FCA2890C00BEE64B4DB90115D4790F5 /* Download.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Download.swift; path = Source/Download.swift; sourceTree = ""; }; + A12F78C2F68E04E316FE62951B25D169 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A3E19E8BDBA82BE8168AE37FA9C1BF21 /* Pods-Anyway.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Anyway.release.xcconfig"; sourceTree = ""; }; + A86C68694ADEB912CBAE30E04992C31C /* Pods-Anyway-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Anyway-resources.sh"; sourceTree = ""; }; + BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + BC138B2009D0806DE14B1B07A8C3D06B /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; + BF362BFFDDAD30E97E3E3B6CE28C3EA4 /* SwiftyJSON.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftyJSON.xcconfig; sourceTree = ""; }; + DBD9AFF96447211C236C5738BD1D0BAD /* SwiftyJSON.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = SwiftyJSON.modulemap; sourceTree = ""; }; + DC6C3BEF7CF735F64C406CEE2FE2170E /* Pods-Anyway-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Anyway-umbrella.h"; sourceTree = ""; }; + F5203DAF180A9A8801705F411B711B01 /* Pods-Anyway-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Anyway-dummy.m"; sourceTree = ""; }; + F98A06DF392EC6DD6830BF5AA4AF9442 /* Pods-Anyway-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Anyway-frameworks.sh"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 223322144FDEC013CC9E32DC4D05E9ED /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AFB530721F7A70D52CE2F7733FBB799 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A5AE1D340C4A0691EC28EEA8241C9FCD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 96D99D0C2472535A169DED65CB231CD7 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D8147C8A1D34B3D00F143EED0FF12C91 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F4DB2CCDD9C53C828DDA55136C4D2984 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 01E4F5E45883048586DCBD1A9A00E032 /* Support Files */ = { + isa = PBXGroup; + children = ( + 228E0F0E166A1E539381EFAB03E354B9 /* Alamofire.modulemap */, + 392E165A73DB131921E6A845CE94EC4D /* Alamofire.xcconfig */, + 929F928737A2DD10C19B5272376CF1D4 /* Alamofire-dummy.m */, + 0D9B065AEB32C6D6C9C3C9D3815DDAE4 /* Alamofire-prefix.pch */, + 38F6AFABD8420CA898ACB691C6B8FF25 /* Alamofire-umbrella.h */, + 64307F8CAA61F9E4AFAEBB14B183158A /* Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/Alamofire"; + sourceTree = ""; + }; + 12B5B987197718386955953C44E629EB /* SwiftyJSON */ = { + isa = PBXGroup; + children = ( + 5F6028763A0FDEC1A4336E309A8F8E5C /* SwiftyJSON.swift */, + 17A0B96C67F923021CAEDFEB5CD24F1E /* Support Files */, + ); + path = SwiftyJSON; + sourceTree = ""; + }; + 159AE37EB87BC0A1BAE28FB6DDDAD1FD /* Pods-Anyway */ = { + isa = PBXGroup; + children = ( + A12F78C2F68E04E316FE62951B25D169 /* Info.plist */, + 222C4BF8B3694CDFD93F89C630851F89 /* Pods-Anyway.modulemap */, + 46E6854A887E54294337DE1E1DC716B1 /* Pods-Anyway-acknowledgements.markdown */, + 27D9D9A0B31ECE9B436F57B6B414091C /* Pods-Anyway-acknowledgements.plist */, + F5203DAF180A9A8801705F411B711B01 /* Pods-Anyway-dummy.m */, + F98A06DF392EC6DD6830BF5AA4AF9442 /* Pods-Anyway-frameworks.sh */, + A86C68694ADEB912CBAE30E04992C31C /* Pods-Anyway-resources.sh */, + DC6C3BEF7CF735F64C406CEE2FE2170E /* Pods-Anyway-umbrella.h */, + 5A974C2BACAA1BC8BD9AB9085A77C421 /* Pods-Anyway.debug.xcconfig */, + A3E19E8BDBA82BE8168AE37FA9C1BF21 /* Pods-Anyway.release.xcconfig */, + ); + name = "Pods-Anyway"; + path = "Target Support Files/Pods-Anyway"; + sourceTree = ""; + }; + 17A0B96C67F923021CAEDFEB5CD24F1E /* Support Files */ = { + isa = PBXGroup; + children = ( + 0B5EE877B751F23E44E957B559809840 /* Info.plist */, + DBD9AFF96447211C236C5738BD1D0BAD /* SwiftyJSON.modulemap */, + BF362BFFDDAD30E97E3E3B6CE28C3EA4 /* SwiftyJSON.xcconfig */, + 01F5B931D671EF0284A9FF39CFD89449 /* SwiftyJSON-dummy.m */, + 59BBFF8F921F0F0084872CF6561D9114 /* SwiftyJSON-prefix.pch */, + 5005C61ED0B8DE99B71D469F0B33C0CF /* SwiftyJSON-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/SwiftyJSON"; + sourceTree = ""; + }; + 375BF8FB088BB3E7DAA9909EDAB47328 /* Products */ = { + isa = PBXGroup; + children = ( + 168DD39527971FC3D53B64CEFBBCE997 /* Alamofire.framework */, + 83CFE890CC09E0FC5F3802F9E36459B5 /* Pods_Anyway.framework */, + 36F10C5F4DF603A640699C63C7474C9A /* SwiftyJSON.framework */, + ); + name = Products; + sourceTree = ""; + }; + 5B19B5B98EA301B26E4BF981F18A3491 /* Alamofire */ = { + isa = PBXGroup; + children = ( + BC138B2009D0806DE14B1B07A8C3D06B /* Alamofire.swift */, + 9FCA2890C00BEE64B4DB90115D4790F5 /* Download.swift */, + 0F8ECF00A31C551E81F4AFAE58AE28DE /* Error.swift */, + 31225DFA1BEE8243D0A545D351C664A4 /* Manager.swift */, + 7D38096C0BF680EE3C06CE2ED8DE0284 /* MultipartFormData.swift */, + 8EEA8AA3C5CCFDDFA31E722F8210645A /* ParameterEncoding.swift */, + 26B92EB57AB62D49F40AAD98AE5B7BC0 /* Request.swift */, + 48AB7332B62C88E1579623BFA20BF7B0 /* Response.swift */, + 33E70390B0FF97AE9E4C1834B5A57F68 /* ResponseSerialization.swift */, + 803AFFFB74C7FCB5BB03AD0CC971D410 /* Result.swift */, + 119593604F171C523758B40F13989E56 /* ServerTrustPolicy.swift */, + 824A2DB65E4419058AC364755541F4A1 /* Stream.swift */, + 1241D90DB9BE97B82FFB08B1946F6339 /* Upload.swift */, + 99DCC27A63156824B4B3D7258B79F3CA /* Validation.swift */, + 01E4F5E45883048586DCBD1A9A00E032 /* Support Files */, + ); + path = Alamofire; + sourceTree = ""; + }; + 7DB346D0F39D3F0E887471402A8071AB = { + isa = PBXGroup; + children = ( + BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */, + BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, + B2522FB691E54C400C1D43FC522FD586 /* Pods */, + 375BF8FB088BB3E7DAA9909EDAB47328 /* Products */, + CE63AFFA15CF231389F8D331D7024D58 /* Targets Support Files */, + ); + sourceTree = ""; + }; + B2522FB691E54C400C1D43FC522FD586 /* Pods */ = { + isa = PBXGroup; + children = ( + 5B19B5B98EA301B26E4BF981F18A3491 /* Alamofire */, + 12B5B987197718386955953C44E629EB /* SwiftyJSON */, + ); + name = Pods; + sourceTree = ""; + }; + BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = { + isa = PBXGroup; + children = ( + BF6342C8B29F4CEEA088EFF7AB4DE362 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + BF6342C8B29F4CEEA088EFF7AB4DE362 /* iOS */ = { + isa = PBXGroup; + children = ( + 3E4E89230EF59BC255123B67864ACF77 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + CE63AFFA15CF231389F8D331D7024D58 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 159AE37EB87BC0A1BAE28FB6DDDAD1FD /* Pods-Anyway */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 36CC43F6BB6DF509C474F362D18E17D6 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5A5D9C98B5985F6C030B1792AE9D553F /* Pods-Anyway-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5F7B61281F714E2A64A51E80A2C9C062 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2C5450AC69398958CF6F7539EF7D99E5 /* Alamofire-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8EEB3715A6519ED1AA5CD8E6012C4BDE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C6B4D108A428C3A7AD2704799BA3735A /* SwiftyJSON-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 432ECC54282C84882B482CCB4CF227FC /* Alamofire */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8B2B2DA2F7F80D41B1FDB5FACFA4B3DE /* Build configuration list for PBXNativeTarget "Alamofire" */; + buildPhases = ( + EF659EFF40D426A3A32A82CDB98CC6EE /* Sources */, + A5AE1D340C4A0691EC28EEA8241C9FCD /* Frameworks */, + 5F7B61281F714E2A64A51E80A2C9C062 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Alamofire; + productName = Alamofire; + productReference = 168DD39527971FC3D53B64CEFBBCE997 /* Alamofire.framework */; + productType = "com.apple.product-type.framework"; + }; + 558BE3D749CAE3482D79E7F4F1AB6DE3 /* Pods-Anyway */ = { + isa = PBXNativeTarget; + buildConfigurationList = 44D3D4C3763630A381097AC97A8A1D45 /* Build configuration list for PBXNativeTarget "Pods-Anyway" */; + buildPhases = ( + 53F7FBA96EA50A68BFD8BF51F7A28D6B /* Sources */, + 223322144FDEC013CC9E32DC4D05E9ED /* Frameworks */, + 36CC43F6BB6DF509C474F362D18E17D6 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + F087FDAD7A0FB849E2B29E92417EC5D8 /* PBXTargetDependency */, + 61034D7013F3D4314A2B84197EF8DD68 /* PBXTargetDependency */, + ); + name = "Pods-Anyway"; + productName = "Pods-Anyway"; + productReference = 83CFE890CC09E0FC5F3802F9E36459B5 /* Pods_Anyway.framework */; + productType = "com.apple.product-type.framework"; + }; + 6DE59327662671F8AEDB2C1C95256F9D /* SwiftyJSON */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2B3742AED2F47C8B8DAF118D450010A7 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */; + buildPhases = ( + 7A2D5C87B80FD41DBCA72C40EF31B55F /* Sources */, + D8147C8A1D34B3D00F143EED0FF12C91 /* Frameworks */, + 8EEB3715A6519ED1AA5CD8E6012C4BDE /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftyJSON; + productName = SwiftyJSON; + productReference = 36F10C5F4DF603A640699C63C7474C9A /* SwiftyJSON.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + }; + buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 7DB346D0F39D3F0E887471402A8071AB; + productRefGroup = 375BF8FB088BB3E7DAA9909EDAB47328 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 432ECC54282C84882B482CCB4CF227FC /* Alamofire */, + 558BE3D749CAE3482D79E7F4F1AB6DE3 /* Pods-Anyway */, + 6DE59327662671F8AEDB2C1C95256F9D /* SwiftyJSON */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 53F7FBA96EA50A68BFD8BF51F7A28D6B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66AC9ABC2B5BA5A8413C8FA8BCF6F2E5 /* Pods-Anyway-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7A2D5C87B80FD41DBCA72C40EF31B55F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5716F2949A097F0AE0748CF27E2D2CAF /* SwiftyJSON-dummy.m in Sources */, + D5A76944FC77C05BE91089C0DA494C1B /* SwiftyJSON.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EF659EFF40D426A3A32A82CDB98CC6EE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B6D2DC3E3DA44CD382B9B425F40E11C1 /* Alamofire-dummy.m in Sources */, + B0FB4B01682814B9E3D32F9DC4A5E762 /* Alamofire.swift in Sources */, + A3505FA2FB3067D53847AD288AC04F03 /* Download.swift in Sources */, + D75CA395D510E08C404E55F5BDAE55CE /* Error.swift in Sources */, + A2C172FE407C0BC3478ADCA91A6C9CEC /* Manager.swift in Sources */, + 8399DBEE3E2D98EB1F466132E476F4D9 /* MultipartFormData.swift in Sources */, + 4DE5FCC41D100B113B6645EA64410F16 /* ParameterEncoding.swift in Sources */, + FC14480CECE872865A9C6E584F886DA3 /* Request.swift in Sources */, + 03F494989CC1A8857B68A317D5D6860F /* Response.swift in Sources */, + D21B7325B3642887BFBE977E021F2D26 /* ResponseSerialization.swift in Sources */, + 82971968CBDAB224212EEB4607C9FB8D /* Result.swift in Sources */, + 80F496237530D382A045A29654D8C11C /* ServerTrustPolicy.swift in Sources */, + FEF0D7653948988B804226129471C1EC /* Stream.swift in Sources */, + 0681ADC8BAE2C3185F13487BAAB4D9DD /* Upload.swift in Sources */, + C75519F0450166A6F28126ECC7664E9C /* Validation.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 61034D7013F3D4314A2B84197EF8DD68 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SwiftyJSON; + target = 6DE59327662671F8AEDB2C1C95256F9D /* SwiftyJSON */; + targetProxy = 00A74EC356DE13C2EBC455DE489B3336 /* PBXContainerItemProxy */; + }; + F087FDAD7A0FB849E2B29E92417EC5D8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Alamofire; + target = 432ECC54282C84882B482CCB4CF227FC /* Alamofire */; + targetProxy = BFFC33DFF32EAD3077930D1A99B8FB27 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 05D328633A91FC58DAEB8A5918BA7109 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BF362BFFDDAD30E97E3E3B6CE28C3EA4 /* SwiftyJSON.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/SwiftyJSON/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = SwiftyJSON; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 389F5D134063A6A1FAB0336104F5F560 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5A974C2BACAA1BC8BD9AB9085A77C421 /* Pods-Anyway.debug.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + INFOPLIST_FILE = "Target Support Files/Pods-Anyway/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-Anyway/Pods-Anyway.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_NAME = Pods_Anyway; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 94EA2253D7F13EE35686D88F88C00080 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3E19E8BDBA82BE8168AE37FA9C1BF21 /* Pods-Anyway.release.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + INFOPLIST_FILE = "Target Support Files/Pods-Anyway/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-Anyway/Pods-Anyway.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_NAME = Pods_Anyway; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + A3427C918F24207B5FD3A44958345D7E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 392E165A73DB131921E6A845CE94EC4D /* Alamofire.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + A70CDAD61F90AC503C7D04CC22DA2923 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + ONLY_ACTIVE_ARCH = YES; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + C993D8405F342C8E23BAD4EDF32D79E0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 392E165A73DB131921E6A845CE94EC4D /* Alamofire.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + F1F76BC25348471F498795C09D880FA6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BF362BFFDDAD30E97E3E3B6CE28C3EA4 /* SwiftyJSON.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/SwiftyJSON/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = SwiftyJSON; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + FB45FFD90572718D82AB9092B750F0CA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = "RELEASE=1"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2B3742AED2F47C8B8DAF118D450010A7 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05D328633A91FC58DAEB8A5918BA7109 /* Debug */, + F1F76BC25348471F498795C09D880FA6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A70CDAD61F90AC503C7D04CC22DA2923 /* Debug */, + FB45FFD90572718D82AB9092B750F0CA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 44D3D4C3763630A381097AC97A8A1D45 /* Build configuration list for PBXNativeTarget "Pods-Anyway" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 389F5D134063A6A1FAB0336104F5F560 /* Debug */, + 94EA2253D7F13EE35686D88F88C00080 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8B2B2DA2F7F80D41B1FDB5FACFA4B3DE /* Build configuration list for PBXNativeTarget "Alamofire" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C993D8405F342C8E23BAD4EDF32D79E0 /* Debug */, + A3427C918F24207B5FD3A44958345D7E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; +} diff --git a/Pods/SwiftyJSON/LICENSE b/Pods/SwiftyJSON/LICENSE new file mode 100644 index 0000000..a7af196 --- /dev/null +++ b/Pods/SwiftyJSON/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ruoyu Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Pods/SwiftyJSON/README.md b/Pods/SwiftyJSON/README.md new file mode 100644 index 0000000..20058d1 --- /dev/null +++ b/Pods/SwiftyJSON/README.md @@ -0,0 +1,379 @@ +#SwiftyJSON [中文介绍](http://tangplin.github.io/swiftyjson/) + +SwiftyJSON makes it easy to deal with JSON data in Swift. + +1. [Why is the typical JSON handling in Swift NOT good](#why-is-the-typical-json-handling-in-swift-not-good) +1. [Requirements](#requirements) +1. [Integration](#integration) +1. [Usage](#usage) + - [Initialization](#initialization) + - [Subscript](#subscript) + - [Loop](#loop) + - [Error](#error) + - [Optional getter](#optional-getter) + - [Non-optional getter](#non-optional-getter) + - [Setter](#setter) + - [Raw object](#raw-object) + - [Literal convertibles](#literal-convertibles) +1. [Work with Alamofire](#work-with-alamofire) + +##Why is the typical JSON handling in Swift NOT good? +Swift is very strict about types. But although explicit typing is good for saving us from mistakes, it becomes painful when dealing with JSON and other areas that are, by nature, implicit about types. + +Take the Twitter API for example. Say we want to retrieve a user's "name" value of some tweet in Swift (according to Twitter's API https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline). + +The code would look like this: + +```swift + +let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) + +if let statusesArray = JSONObject as? [AnyObject], + let status = statusesArray[0] as? [String: AnyObject], + let user = status["user"] as? [String: AnyObject], + let username = user["name"] as? String { + // Finally we got the username +} + +``` + +It's not good. + +Even if we use optional chaining, it would be messy: + +```swift + +let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) + +if let username = (((JSONObject as? [AnyObject])?[0] as? [String: AnyObject])?["user"] as? [String: AnyObject])?["name"] as? String { + // What a disaster +} + +``` +An unreadable mess--for something that should really be simple! + +With SwiftyJSON all you have to do is: + +```swift + +let json = JSON(data: dataFromNetworking) +if let userName = json[0]["user"]["name"].string { + //Now you got your value +} + +``` + +And don't worry about the Optional Wrapping thing. It's done for you automatically. + +```swift + +let json = JSON(data: dataFromNetworking) +if let userName = json[999999]["wrong_key"]["wrong_name"].string { + //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety +} else { + //Print the error + print(json[999999]["wrong_key"]["wrong_name"]) +} + +``` + +## Requirements + +- iOS 7.0+ / Mac OS X 10.9+ +- Xcode 7 + +##Integration + +####CocoaPods (iOS 8+, OS X 10.9+) +You can use [Cocoapods](http://cocoapods.org/) to install `SwiftyJSON`by adding it to your `Podfile`: +```ruby +platform :ios, '8.0' +use_frameworks! + +target 'MyApp' do + pod 'SwiftyJSON', :git => 'https://github.com/SwiftyJSON/SwiftyJSON.git' +end +``` +Note that this requires CocoaPods version 36, and your iOS deployment target to be at least 8.0: + +####Carthage (iOS 8+, OS X 10.9+) +You can use [Carthage](https://github.com/Carthage/Carthage) to install `SwiftyJSON` by adding it to your `Cartfile`: +``` +github "SwiftyJSON/SwiftyJSON" +``` + +####Manually (iOS 7+, OS X 10.9+) + +To use this library in your project manually you may: + +1. for Projects, just drag SwiftyJSON.swift to the project tree +2. for Workspaces, include the whole SwiftyJSON.xcodeproj + +## Usage + +####Initialization +```swift +import SwiftyJSON +``` +```swift +let json = JSON(data: dataFromNetworking) +``` +```swift +let json = JSON(jsonObject) +``` +```swift +if let dataFromString = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { + let json = JSON(data: dataFromString) +} +``` + +####Subscript +```swift +//Getting a double from a JSON Array +let name = json[0].double +``` +```swift +//Getting a string from a JSON Dictionary +let name = json["name"].stringValue +``` +```swift +//Getting a string using a path to the element +let path = [1,"list",2,"name"] +let name = json[path].string +//Just the same +let name = json[1]["list"][2]["name"].string +//Alternatively +let name = json[1,"list",2,"name"].string +``` +```swift +//With a hard way +let name = json[].string +``` +```swift +//With a custom way +let keys:[SubscriptType] = [1,"list",2,"name"] +let name = json[keys].string +``` +####Loop +```swift +//If json is .Dictionary +for (key,subJson):(String, JSON) in json { + //Do something you want +} +``` +*The first element is always a String, even if the JSON is an Array* +```swift +//If json is .Array +//The `index` is 0.. = json["list"].arrayValue +``` +```swift +//If not a Dictionary or nil, return [:] +let user: Dictionary = json["user"].dictionaryValue +``` + +####Setter +```swift +json["name"] = JSON("new-name") +json[0] = JSON(1) +``` +```swift +json["id"].int = 1234567890 +json["coordinate"].double = 8766.766 +json["name"].string = "Jack" +json.arrayObject = [1,2,3,4] +json.dictionary = ["name":"Jack", "age":25] +``` + +####Raw object +```swift +let jsonObject: AnyObject = json.object +``` +```swift +if let jsonObject: AnyObject = json.rawValue +``` +```swift +//convert the JSON to raw NSData +if let data = json.rawData() { + //Do something you want +} +``` +```swift +//convert the JSON to a raw String +if let string = json.rawString() { + //Do something you want +} + +####Existance +```swift +//shows you whether value specified in JSON or not +if json["name"].isExists() +``` +``` +####Literal convertibles +For more info about literal convertibles: [Swift Literal Convertibles](http://nshipster.com/swift-literal-convertible/) +```swift +//StringLiteralConvertible +let json: JSON = "I'm a json" +``` +```swift +//IntegerLiteralConvertible +let json: JSON = 12345 +``` +```swift +//BooleanLiteralConvertible +let json: JSON = true +``` +```swift +//FloatLiteralConvertible +let json: JSON = 2.8765 +``` +```swift +//DictionaryLiteralConvertible +let json: JSON = ["I":"am", "a":"json"] +``` +```swift +//ArrayLiteralConvertible +let json: JSON = ["I", "am", "a", "json"] +``` +```swift +//NilLiteralConvertible +let json: JSON = nil +``` +```swift +//With subscript in array +var json: JSON = [1,2,3] +json[0] = 100 +json[1] = 200 +json[2] = 300 +json[999] = 300 //Don't worry, nothing will happen +``` +```swift +//With subscript in dictionary +var json: JSON = ["name": "Jack", "age": 25] +json["name"] = "Mike" +json["age"] = "25" //It's OK to set String +json["address"] = "L.A." // Add the "address": "L.A." in json +``` +```swift +//Array & Dictionary +var json: JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]] +json["list"][3]["what"] = "that" +json["list",3,"what"] = "that" +let path = ["list",3,"what"] +json[path] = "that" +``` +##Work with Alamofire + +SwiftyJSON nicely wraps the result of the Alamofire JSON response handler: +```swift +Alamofire.request(.GET, url, parameters: parameters) + .responseJSON { (req, res, json, error) in + if(error != nil) { + NSLog("Error: \(error)") + print(req) + print(res) + } + else { + NSLog("Success: \(url)") + var json = JSON(json!) + } + } +``` diff --git a/Pods/SwiftyJSON/Source/SwiftyJSON.swift b/Pods/SwiftyJSON/Source/SwiftyJSON.swift new file mode 100644 index 0000000..a63ef84 --- /dev/null +++ b/Pods/SwiftyJSON/Source/SwiftyJSON.swift @@ -0,0 +1,1352 @@ +// SwiftyJSON.swift +// +// Copyright (c) 2014 Ruoyu Fu, Pinglin Tang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +// MARK: - Error + +///Error domain +public let ErrorDomain: String! = "SwiftyJSONErrorDomain" + +///Error code +public let ErrorUnsupportedType: Int! = 999 +public let ErrorIndexOutOfBounds: Int! = 900 +public let ErrorWrongType: Int! = 901 +public let ErrorNotExist: Int! = 500 +public let ErrorInvalidJSON: Int! = 490 + +// MARK: - JSON Type + +/** +JSON's type definitions. + +See http://tools.ietf.org/html/rfc7231#section-4.3 +*/ +public enum Type :Int{ + + case Number + case String + case Bool + case Array + case Dictionary + case Null + case Unknown +} + +// MARK: - JSON Base + +public struct JSON { + + /** + Creates a JSON using the data. + + - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary + - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. + - parameter error: error The NSErrorPointer used to return the error. `nil` by default. + + - returns: The created JSON + */ + public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) { + do { + let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt) + self.init(object) + } catch let aError as NSError { + if error != nil { + error.memory = aError + } + self.init(NSNull()) + } + } + + /** + Creates a JSON using the object. + + - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. + + - returns: The created JSON + */ + public init(_ object: AnyObject) { + self.object = object + } + + /** + Creates a JSON from a [JSON] + + - parameter jsonArray: A Swift array of JSON objects + + - returns: The created JSON + */ + public init(_ jsonArray:[JSON]) { + self.init(jsonArray.map { $0.object }) + } + + /** + Creates a JSON from a [String: JSON] + + - parameter jsonDictionary: A Swift dictionary of JSON objects + + - returns: The created JSON + */ + public init(_ jsonDictionary:[String: JSON]) { + var dictionary = [String: AnyObject]() + for (key, json) in jsonDictionary { + dictionary[key] = json.object + } + self.init(dictionary) + } + + /// Private object + private var rawArray: [AnyObject] = [] + private var rawDictionary: [String : AnyObject] = [:] + private var rawString: String = "" + private var rawNumber: NSNumber = 0 + private var rawNull: NSNull = NSNull() + /// Private type + private var _type: Type = .Null + /// prviate error + private var _error: NSError? = nil + + /// Object in JSON + public var object: AnyObject { + get { + switch self.type { + case .Array: + return self.rawArray + case .Dictionary: + return self.rawDictionary + case .String: + return self.rawString + case .Number: + return self.rawNumber + case .Bool: + return self.rawNumber + default: + return self.rawNull + } + } + set { + _error = nil + switch newValue { + case let number as NSNumber: + if number.isBool { + _type = .Bool + } else { + _type = .Number + } + self.rawNumber = number + case let string as String: + _type = .String + self.rawString = string + case _ as NSNull: + _type = .Null + case let array as [AnyObject]: + _type = .Array + self.rawArray = array + case let dictionary as [String : AnyObject]: + _type = .Dictionary + self.rawDictionary = dictionary + default: + _type = .Unknown + _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"]) + } + } + } + + /// json type + public var type: Type { get { return _type } } + + /// Error in JSON + public var error: NSError? { get { return self._error } } + + /// The static null json + @available(*, unavailable, renamed="null") + public static var nullJSON: JSON { get { return null } } + public static var null: JSON { get { return JSON(NSNull()) } } +} + +// MARK: - CollectionType, SequenceType, Indexable +extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable { + + public typealias Generator = JSONGenerator + + public typealias Index = JSONIndex + + public var startIndex: JSON.Index { + switch self.type { + case .Array: + return JSONIndex(arrayIndex: self.rawArray.startIndex) + case .Dictionary: + return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex) + default: + return JSONIndex() + } + } + + public var endIndex: JSON.Index { + switch self.type { + case .Array: + return JSONIndex(arrayIndex: self.rawArray.endIndex) + case .Dictionary: + return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex) + default: + return JSONIndex() + } + } + + public subscript (position: JSON.Index) -> JSON.Generator.Element { + switch self.type { + case .Array: + return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!])) + case .Dictionary: + let (key, value) = self.rawDictionary[position.dictionaryIndex!] + return (key, JSON(value)) + default: + return ("", JSON.null) + } + } + + /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `false`. + public var isEmpty: Bool { + get { + switch self.type { + case .Array: + return self.rawArray.isEmpty + case .Dictionary: + return self.rawDictionary.isEmpty + default: + return true + } + } + } + + /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`. + public var count: Int { + switch self.type { + case .Array: + return self.rawArray.count + case .Dictionary: + return self.rawDictionary.count + default: + return 0 + } + } + + public func underestimateCount() -> Int { + switch self.type { + case .Array: + return self.rawArray.underestimateCount() + case .Dictionary: + return self.rawDictionary.underestimateCount() + default: + return 0 + } + } + + /** + If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty. + + - returns: Return a *generator* over the elements of JSON. + */ + public func generate() -> JSON.Generator { + return JSON.Generator(self) + } +} + +public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable { + + let arrayIndex: Int? + let dictionaryIndex: DictionaryIndex? + + let type: Type + + init(){ + self.arrayIndex = nil + self.dictionaryIndex = nil + self.type = .Unknown + } + + init(arrayIndex: Int) { + self.arrayIndex = arrayIndex + self.dictionaryIndex = nil + self.type = .Array + } + + init(dictionaryIndex: DictionaryIndex) { + self.arrayIndex = nil + self.dictionaryIndex = dictionaryIndex + self.type = .Dictionary + } + + public func successor() -> JSONIndex { + switch self.type { + case .Array: + return JSONIndex(arrayIndex: self.arrayIndex!.successor()) + case .Dictionary: + return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor()) + default: + return JSONIndex() + } + } +} + +public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool { + switch (lhs.type, rhs.type) { + case (.Array, .Array): + return lhs.arrayIndex == rhs.arrayIndex + case (.Dictionary, .Dictionary): + return lhs.dictionaryIndex == rhs.dictionaryIndex + default: + return false + } +} + +public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool { + switch (lhs.type, rhs.type) { + case (.Array, .Array): + return lhs.arrayIndex < rhs.arrayIndex + case (.Dictionary, .Dictionary): + return lhs.dictionaryIndex < rhs.dictionaryIndex + default: + return false + } +} + +public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { + switch (lhs.type, rhs.type) { + case (.Array, .Array): + return lhs.arrayIndex <= rhs.arrayIndex + case (.Dictionary, .Dictionary): + return lhs.dictionaryIndex <= rhs.dictionaryIndex + default: + return false + } +} + +public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { + switch (lhs.type, rhs.type) { + case (.Array, .Array): + return lhs.arrayIndex >= rhs.arrayIndex + case (.Dictionary, .Dictionary): + return lhs.dictionaryIndex >= rhs.dictionaryIndex + default: + return false + } +} + +public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool { + switch (lhs.type, rhs.type) { + case (.Array, .Array): + return lhs.arrayIndex > rhs.arrayIndex + case (.Dictionary, .Dictionary): + return lhs.dictionaryIndex > rhs.dictionaryIndex + default: + return false + } +} + +public struct JSONGenerator : GeneratorType { + + public typealias Element = (String, JSON) + + private let type: Type + private var dictionayGenerate: DictionaryGenerator? + private var arrayGenerate: IndexingGenerator<[AnyObject]>? + private var arrayIndex: Int = 0 + + init(_ json: JSON) { + self.type = json.type + if type == .Array { + self.arrayGenerate = json.rawArray.generate() + }else { + self.dictionayGenerate = json.rawDictionary.generate() + } + } + + public mutating func next() -> JSONGenerator.Element? { + switch self.type { + case .Array: + if let o = self.arrayGenerate!.next() { + return (String(self.arrayIndex++), JSON(o)) + } else { + return nil + } + case .Dictionary: + if let (k, v): (String, AnyObject) = self.dictionayGenerate!.next() { + return (k, JSON(v)) + } else { + return nil + } + default: + return nil + } + } +} + +// MARK: - Subscript + +/** +* To mark both String and Int can be used in subscript. +*/ +public protocol JSONSubscriptType {} + +extension Int: JSONSubscriptType {} + +extension String: JSONSubscriptType {} + +extension JSON { + + /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error. + private subscript(index index: Int) -> JSON { + get { + if self.type != .Array { + var r = JSON.null + r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"]) + return r + } else if index >= 0 && index < self.rawArray.count { + return JSON(self.rawArray[index]) + } else { + var r = JSON.null + r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) + return r + } + } + set { + if self.type == .Array { + if self.rawArray.count > index && newValue.error == nil { + self.rawArray[index] = newValue.object + } + } + } + } + + /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error. + private subscript(key key: String) -> JSON { + get { + var r = JSON.null + if self.type == .Dictionary { + if let o = self.rawDictionary[key] { + r = JSON(o) + } else { + r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"]) + } + } else { + r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"]) + } + return r + } + set { + if self.type == .Dictionary && newValue.error == nil { + self.rawDictionary[key] = newValue.object + } + } + } + + /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. + private subscript(sub sub: JSONSubscriptType) -> JSON { + get { + if sub is String { + return self[key:sub as! String] + } else { + return self[index:sub as! Int] + } + } + set { + if sub is String { + self[key:sub as! String] = newValue + } else { + self[index:sub as! Int] = newValue + } + } + } + + /** + Find a json in the complex data structuresby using the Int/String's array. + + - parameter path: The target json's path. Example: + + let json = JSON[data] + let path = [9,"list","person","name"] + let name = json[path] + + The same as: let name = json[9]["list"]["person"]["name"] + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: [JSONSubscriptType]) -> JSON { + get { + return path.reduce(self) { $0[sub: $1] } + } + set { + switch path.count { + case 0: + return + case 1: + self[sub:path[0]].object = newValue.object + default: + var aPath = path; aPath.removeAtIndex(0) + var nextJSON = self[sub: path[0]] + nextJSON[aPath] = newValue + self[sub: path[0]] = nextJSON + } + } + } + + /** + Find a json in the complex data structuresby using the Int/String's array. + + - parameter path: The target json's path. Example: + + let name = json[9,"list","person","name"] + + The same as: let name = json[9]["list"]["person"]["name"] + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: JSONSubscriptType...) -> JSON { + get { + return self[path] + } + set { + self[path] = newValue + } + } +} + +// MARK: - LiteralConvertible + +extension JSON: Swift.StringLiteralConvertible { + + public init(stringLiteral value: StringLiteralType) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: StringLiteralType) { + self.init(value) + } + + public init(unicodeScalarLiteral value: StringLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.IntegerLiteralConvertible { + + public init(integerLiteral value: IntegerLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.BooleanLiteralConvertible { + + public init(booleanLiteral value: BooleanLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.FloatLiteralConvertible { + + public init(floatLiteral value: FloatLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.DictionaryLiteralConvertible { + + public init(dictionaryLiteral elements: (String, AnyObject)...) { + self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in + var d = dictionary + d[element.0] = element.1 + return d + }) + } +} + +extension JSON: Swift.ArrayLiteralConvertible { + + public init(arrayLiteral elements: AnyObject...) { + self.init(elements) + } +} + +extension JSON: Swift.NilLiteralConvertible { + + public init(nilLiteral: ()) { + self.init(NSNull()) + } +} + +// MARK: - Raw + +extension JSON: Swift.RawRepresentable { + + public init?(rawValue: AnyObject) { + if JSON(rawValue).type == .Unknown { + return nil + } else { + self.init(rawValue) + } + } + + public var rawValue: AnyObject { + return self.object + } + + public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData { + guard NSJSONSerialization.isValidJSONObject(self.object) else { + throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"]) + } + + return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt) + } + + public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? { + switch self.type { + case .Array, .Dictionary: + do { + let data = try self.rawData(options: opt) + return NSString(data: data, encoding: encoding) as? String + } catch _ { + return nil + } + case .String: + return self.rawString + case .Number: + return self.rawNumber.stringValue + case .Bool: + return self.rawNumber.boolValue.description + case .Null: + return "null" + default: + return nil + } + } +} + +// MARK: - Printable, DebugPrintable + +extension JSON: Swift.Printable, Swift.DebugPrintable { + + public var description: String { + if let string = self.rawString(options:.PrettyPrinted) { + return string + } else { + return "unknown" + } + } + + public var debugDescription: String { + return description + } +} + +// MARK: - Array + +extension JSON { + + //Optional [JSON] + public var array: [JSON]? { + get { + if self.type == .Array { + return self.rawArray.map{ JSON($0) } + } else { + return nil + } + } + } + + //Non-optional [JSON] + public var arrayValue: [JSON] { + get { + return self.array ?? [] + } + } + + //Optional [AnyObject] + public var arrayObject: [AnyObject]? { + get { + switch self.type { + case .Array: + return self.rawArray + default: + return nil + } + } + set { + if let array = newValue { + self.object = array + } else { + self.object = NSNull() + } + } + } +} + +// MARK: - Dictionary + +extension JSON { + + //Optional [String : JSON] + public var dictionary: [String : JSON]? { + if self.type == .Dictionary { + return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in + var d = dictionary + d[element.0] = JSON(element.1) + return d + } + } else { + return nil + } + } + + //Non-optional [String : JSON] + public var dictionaryValue: [String : JSON] { + return self.dictionary ?? [:] + } + + //Optional [String : AnyObject] + public var dictionaryObject: [String : AnyObject]? { + get { + switch self.type { + case .Dictionary: + return self.rawDictionary + default: + return nil + } + } + set { + if let v = newValue { + self.object = v + } else { + self.object = NSNull() + } + } + } +} + +// MARK: - Bool + +extension JSON: Swift.BooleanType { + + //Optional bool + public var bool: Bool? { + get { + switch self.type { + case .Bool: + return self.rawNumber.boolValue + default: + return nil + } + } + set { + if newValue != nil { + self.object = NSNumber(bool: newValue!) + } else { + self.object = NSNull() + } + } + } + + //Non-optional bool + public var boolValue: Bool { + get { + switch self.type { + case .Bool, .Number, .String: + return self.object.boolValue + default: + return false + } + } + set { + self.object = NSNumber(bool: newValue) + } + } +} + +// MARK: - String + +extension JSON { + + //Optional string + public var string: String? { + get { + switch self.type { + case .String: + return self.object as? String + default: + return nil + } + } + set { + if newValue != nil { + self.object = NSString(string:newValue!) + } else { + self.object = NSNull() + } + } + } + + //Non-optional string + public var stringValue: String { + get { + switch self.type { + case .String: + return self.object as! String + case .Number: + return self.object.stringValue + case .Bool: + return (self.object as! Bool).description + default: + return "" + } + } + set { + self.object = NSString(string:newValue) + } + } +} + +// MARK: - Number +extension JSON { + + //Optional number + public var number: NSNumber? { + get { + switch self.type { + case .Number, .Bool: + return self.rawNumber + default: + return nil + } + } + set { + self.object = newValue ?? NSNull() + } + } + + //Non-optional number + public var numberValue: NSNumber { + get { + switch self.type { + case .String: + let decimal = NSDecimalNumber(string: self.object as? String) + if decimal == NSDecimalNumber.notANumber() { // indicates parse error + return NSDecimalNumber.zero() + } + return decimal + case .Number, .Bool: + return self.object as! NSNumber + default: + return NSNumber(double: 0.0) + } + } + set { + self.object = newValue + } + } +} + +//MARK: - Null +extension JSON { + + public var null: NSNull? { + get { + switch self.type { + case .Null: + return self.rawNull + default: + return nil + } + } + set { + self.object = NSNull() + } + } + public func isExists() -> Bool{ + if let errorValue = error where errorValue.code == ErrorNotExist{ + return false + } + return true + } +} + +//MARK: - URL +extension JSON { + + //Optional URL + public var URL: NSURL? { + get { + switch self.type { + case .String: + if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) { + return NSURL(string: encodedString_) + } else { + return nil + } + default: + return nil + } + } + set { + self.object = newValue?.absoluteString ?? NSNull() + } + } +} + +// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 + +extension JSON { + + public var double: Double? { + get { + return self.number?.doubleValue + } + set { + if newValue != nil { + self.object = NSNumber(double: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var doubleValue: Double { + get { + return self.numberValue.doubleValue + } + set { + self.object = NSNumber(double: newValue) + } + } + + public var float: Float? { + get { + return self.number?.floatValue + } + set { + if newValue != nil { + self.object = NSNumber(float: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var floatValue: Float { + get { + return self.numberValue.floatValue + } + set { + self.object = NSNumber(float: newValue) + } + } + + public var int: Int? { + get { + return self.number?.longValue + } + set { + if newValue != nil { + self.object = NSNumber(integer: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var intValue: Int { + get { + return self.numberValue.integerValue + } + set { + self.object = NSNumber(integer: newValue) + } + } + + public var uInt: UInt? { + get { + return self.number?.unsignedLongValue + } + set { + if newValue != nil { + self.object = NSNumber(unsignedLong: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var uIntValue: UInt { + get { + return self.numberValue.unsignedLongValue + } + set { + self.object = NSNumber(unsignedLong: newValue) + } + } + + public var int8: Int8? { + get { + return self.number?.charValue + } + set { + if newValue != nil { + self.object = NSNumber(char: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var int8Value: Int8 { + get { + return self.numberValue.charValue + } + set { + self.object = NSNumber(char: newValue) + } + } + + public var uInt8: UInt8? { + get { + return self.number?.unsignedCharValue + } + set { + if newValue != nil { + self.object = NSNumber(unsignedChar: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var uInt8Value: UInt8 { + get { + return self.numberValue.unsignedCharValue + } + set { + self.object = NSNumber(unsignedChar: newValue) + } + } + + public var int16: Int16? { + get { + return self.number?.shortValue + } + set { + if newValue != nil { + self.object = NSNumber(short: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var int16Value: Int16 { + get { + return self.numberValue.shortValue + } + set { + self.object = NSNumber(short: newValue) + } + } + + public var uInt16: UInt16? { + get { + return self.number?.unsignedShortValue + } + set { + if newValue != nil { + self.object = NSNumber(unsignedShort: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var uInt16Value: UInt16 { + get { + return self.numberValue.unsignedShortValue + } + set { + self.object = NSNumber(unsignedShort: newValue) + } + } + + public var int32: Int32? { + get { + return self.number?.intValue + } + set { + if newValue != nil { + self.object = NSNumber(int: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var int32Value: Int32 { + get { + return self.numberValue.intValue + } + set { + self.object = NSNumber(int: newValue) + } + } + + public var uInt32: UInt32? { + get { + return self.number?.unsignedIntValue + } + set { + if newValue != nil { + self.object = NSNumber(unsignedInt: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var uInt32Value: UInt32 { + get { + return self.numberValue.unsignedIntValue + } + set { + self.object = NSNumber(unsignedInt: newValue) + } + } + + public var int64: Int64? { + get { + return self.number?.longLongValue + } + set { + if newValue != nil { + self.object = NSNumber(longLong: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var int64Value: Int64 { + get { + return self.numberValue.longLongValue + } + set { + self.object = NSNumber(longLong: newValue) + } + } + + public var uInt64: UInt64? { + get { + return self.number?.unsignedLongLongValue + } + set { + if newValue != nil { + self.object = NSNumber(unsignedLongLong: newValue!) + } else { + self.object = NSNull() + } + } + } + + public var uInt64Value: UInt64 { + get { + return self.numberValue.unsignedLongLongValue + } + set { + self.object = NSNumber(unsignedLongLong: newValue) + } + } +} + +//MARK: - Comparable +extension JSON : Swift.Comparable {} + +public func ==(lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.Number, .Number): + return lhs.rawNumber == rhs.rawNumber + case (.String, .String): + return lhs.rawString == rhs.rawString + case (.Bool, .Bool): + return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue + case (.Array, .Array): + return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.Dictionary, .Dictionary): + return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.Null, .Null): + return true + default: + return false + } +} + +public func <=(lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.Number, .Number): + return lhs.rawNumber <= rhs.rawNumber + case (.String, .String): + return lhs.rawString <= rhs.rawString + case (.Bool, .Bool): + return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue + case (.Array, .Array): + return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.Dictionary, .Dictionary): + return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.Null, .Null): + return true + default: + return false + } +} + +public func >=(lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.Number, .Number): + return lhs.rawNumber >= rhs.rawNumber + case (.String, .String): + return lhs.rawString >= rhs.rawString + case (.Bool, .Bool): + return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue + case (.Array, .Array): + return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.Dictionary, .Dictionary): + return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.Null, .Null): + return true + default: + return false + } +} + +public func >(lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.Number, .Number): + return lhs.rawNumber > rhs.rawNumber + case (.String, .String): + return lhs.rawString > rhs.rawString + default: + return false + } +} + +public func <(lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.Number, .Number): + return lhs.rawNumber < rhs.rawNumber + case (.String, .String): + return lhs.rawString < rhs.rawString + default: + return false + } +} + +private let trueNumber = NSNumber(bool: true) +private let falseNumber = NSNumber(bool: false) +private let trueObjCType = String.fromCString(trueNumber.objCType) +private let falseObjCType = String.fromCString(falseNumber.objCType) + +// MARK: - NSNumber: Comparable + +extension NSNumber { + var isBool:Bool { + get { + let objCType = String.fromCString(self.objCType) + if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType) + || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){ + return true + } else { + return false + } + } + } +} + +public func ==(lhs: NSNumber, rhs: NSNumber) -> Bool { + switch (lhs.isBool, rhs.isBool) { + case (false, true): + return false + case (true, false): + return false + default: + return lhs.compare(rhs) == NSComparisonResult.OrderedSame + } +} + +public func !=(lhs: NSNumber, rhs: NSNumber) -> Bool { + return !(lhs == rhs) +} + +public func <(lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): + return false + case (true, false): + return false + default: + return lhs.compare(rhs) == NSComparisonResult.OrderedAscending + } +} + +public func >(lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): + return false + case (true, false): + return false + default: + return lhs.compare(rhs) == NSComparisonResult.OrderedDescending + } +} + +public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): + return false + case (true, false): + return false + default: + return lhs.compare(rhs) != NSComparisonResult.OrderedDescending + } +} + +public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): + return false + case (true, false): + return false + default: + return lhs.compare(rhs) != NSComparisonResult.OrderedAscending + } +} diff --git a/Pods/Target Support Files/Alamofire/Alamofire-dummy.m b/Pods/Target Support Files/Alamofire/Alamofire-dummy.m new file mode 100644 index 0000000..a6c4594 --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Alamofire-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Alamofire : NSObject +@end +@implementation PodsDummy_Alamofire +@end diff --git a/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch b/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch new file mode 100644 index 0000000..aa992a4 --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h b/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h new file mode 100644 index 0000000..6b71676 --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double AlamofireVersionNumber; +FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; + diff --git a/Pods/Target Support Files/Alamofire/Alamofire.modulemap b/Pods/Target Support Files/Alamofire/Alamofire.modulemap new file mode 100644 index 0000000..d1f125f --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Alamofire.modulemap @@ -0,0 +1,6 @@ +framework module Alamofire { + umbrella header "Alamofire-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Alamofire/Alamofire.xcconfig b/Pods/Target Support Files/Alamofire/Alamofire.xcconfig new file mode 100644 index 0000000..baea9f3 --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Alamofire.xcconfig @@ -0,0 +1,5 @@ +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Alamofire" "${PODS_ROOT}/Headers/Public" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_ROOT = ${SRCROOT} +SKIP_INSTALL = YES \ No newline at end of file diff --git a/Pods/Target Support Files/Alamofire/Info.plist b/Pods/Target Support Files/Alamofire/Info.plist new file mode 100644 index 0000000..60d0f97 --- /dev/null +++ b/Pods/Target Support Files/Alamofire/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.cocoapods.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.1.2 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-Anyway/Info.plist b/Pods/Target Support Files/Pods-Anyway/Info.plist new file mode 100644 index 0000000..6974542 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.cocoapods.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.markdown b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.markdown new file mode 100644 index 0000000..ed32da9 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.markdown @@ -0,0 +1,51 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## Alamofire + +Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +## SwiftyJSON + +The MIT License (MIT) + +Copyright (c) 2014 Ruoyu Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Generated by CocoaPods - http://cocoapods.org diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.plist b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.plist new file mode 100644 index 0000000..606389d --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-acknowledgements.plist @@ -0,0 +1,85 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + Title + Alamofire + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2014 Ruoyu Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + Title + SwiftyJSON + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - http://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-dummy.m b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-dummy.m new file mode 100644 index 0000000..6a6bb71 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_Anyway : NSObject +@end +@implementation PodsDummy_Pods_Anyway +@end diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-frameworks.sh b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-frameworks.sh new file mode 100755 index 0000000..37048df --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-frameworks.sh @@ -0,0 +1,93 @@ +#!/bin/sh +set -e + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # use filter instead of exclude so missing patterns dont' throw errors + echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identitiy + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements \"$1\"" + /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements "$1" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + # Get architectures for current file + archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" + stripped="" + for arch in $archs; do + if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" || exit 1 + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi +} + + +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "Pods-Anyway/Alamofire.framework" + install_framework "Pods-Anyway/SwiftyJSON.framework" +fi +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "Pods-Anyway/Alamofire.framework" + install_framework "Pods-Anyway/SwiftyJSON.framework" +fi diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-resources.sh b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-resources.sh new file mode 100755 index 0000000..16774fb --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-resources.sh @@ -0,0 +1,95 @@ +#!/bin/sh +set -e + +mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +realpath() { + DIRECTORY="$(cd "${1%/*}" && pwd)" + FILENAME="${1##*/}" + echo "$DIRECTORY/$FILENAME" +} + +install_resource() +{ + case $1 in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" + ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" + ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" + ;; + *.framework) + echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" + xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" + xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" + xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + /*) + echo "$1" + echo "$1" >> "$RESOURCES_TO_COPY" + ;; + *) + echo "${PODS_ROOT}/$1" + echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" + ;; + esac +} + +mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] +then + case "${TARGETED_DEVICE_FAMILY}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; + esac + + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "`realpath $PODS_ROOT`*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-umbrella.h b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-umbrella.h new file mode 100644 index 0000000..89a3b10 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double Pods_AnywayVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_AnywayVersionString[]; + diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.debug.xcconfig b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.debug.xcconfig new file mode 100644 index 0000000..5861277 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.debug.xcconfig @@ -0,0 +1,8 @@ +EMBEDDED_CONTENT_CONTAINS_SWIFT = YES +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$CONFIGURATION_BUILD_DIR/Alamofire.framework/Headers" -iquote "$CONFIGURATION_BUILD_DIR/SwiftyJSON.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "SwiftyJSON" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_FRAMEWORK_BUILD_PATH = $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Pods-Anyway +PODS_ROOT = ${SRCROOT}/Pods \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.modulemap b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.modulemap new file mode 100644 index 0000000..cbfc5a9 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.modulemap @@ -0,0 +1,6 @@ +framework module Pods_Anyway { + umbrella header "Pods-Anyway-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.release.xcconfig b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.release.xcconfig new file mode 100644 index 0000000..5861277 --- /dev/null +++ b/Pods/Target Support Files/Pods-Anyway/Pods-Anyway.release.xcconfig @@ -0,0 +1,8 @@ +EMBEDDED_CONTENT_CONTAINS_SWIFT = YES +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$CONFIGURATION_BUILD_DIR/Alamofire.framework/Headers" -iquote "$CONFIGURATION_BUILD_DIR/SwiftyJSON.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "SwiftyJSON" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_FRAMEWORK_BUILD_PATH = $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Pods-Anyway +PODS_ROOT = ${SRCROOT}/Pods \ No newline at end of file diff --git a/Pods/Target Support Files/SwiftyJSON/Info.plist b/Pods/Target Support Files/SwiftyJSON/Info.plist new file mode 100644 index 0000000..a98a41a --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.cocoapods.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 2.3.1 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m new file mode 100644 index 0000000..3159bec --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_SwiftyJSON : NSObject +@end +@implementation PodsDummy_SwiftyJSON +@end diff --git a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch new file mode 100644 index 0000000..aa992a4 --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h new file mode 100644 index 0000000..ce00ad0 --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double SwiftyJSONVersionNumber; +FOUNDATION_EXPORT const unsigned char SwiftyJSONVersionString[]; + diff --git a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap new file mode 100644 index 0000000..6f41751 --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap @@ -0,0 +1,6 @@ +framework module SwiftyJSON { + umbrella header "SwiftyJSON-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.xcconfig b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.xcconfig new file mode 100644 index 0000000..6fb4cff --- /dev/null +++ b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.xcconfig @@ -0,0 +1,5 @@ +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/SwiftyJSON" "${PODS_ROOT}/Headers/Public" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_ROOT = ${SRCROOT} +SKIP_INSTALL = YES \ No newline at end of file