├── PackageTracker ├── USPSConfig.json ├── PackageTracker │ ├── USPSConfig.json │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── clock.imageset │ │ │ ├── 11-clock@2x.png │ │ │ └── Contents.json │ │ ├── disabledClock.imageset │ │ │ ├── disabledClock@2x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Result.swift │ ├── PersistenceControllerAccessible.swift │ ├── Package.swift │ ├── PackageManager.swift │ ├── Package+CoreDataProperties.swift │ ├── PackageModel.xcdatamodeld │ │ └── PackageModel.xcdatamodel │ │ │ └── contents │ ├── PadSplitViewController.swift │ ├── USPSManager.swift │ ├── USPSCommunicator.swift │ ├── SampleUSPSXML.xml │ ├── AppDelegate.swift │ ├── Info.swift │ ├── Detail.swift │ ├── Summary.swift │ ├── Info.plist │ ├── ViewController.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ └── TrackingHistoryViewController.swift ├── PackageTrackerTests │ ├── MockPackageManager.swift │ ├── Info.plist │ ├── USPSRequestInfo.swift │ ├── ViewControllerTests.swift │ ├── TrackingHistoryViewControllerTests.swift │ ├── TestInfo.swift │ ├── PadSplitViewControllerTests.swift │ └── USPSRequestFlow.swift ├── PersistenceController.swift └── PackageTracker.xcodeproj │ ├── xcshareddata │ └── xcschemes │ │ └── PackageTracker.xcscheme │ └── project.pbxproj ├── .gitignore ├── README.md └── LICENSE /PackageTracker/USPSConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "userID":"908SIXFI7346", 3 | "testPackageID":"1234567890" 4 | } 5 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/USPSConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "userID":"908SIXFI7346", 3 | "packageID":"1234567890" 4 | } 5 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/clock.imageset/11-clock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConceptsInCode/PackageTracker/HEAD/PackageTracker/PackageTracker/Images.xcassets/clock.imageset/11-clock@2x.png -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/disabledClock.imageset/disabledClock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConceptsInCode/PackageTracker/HEAD/PackageTracker/PackageTracker/Images.xcassets/disabledClock.imageset/disabledClock@2x.png -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 6/5/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Result { 12 | case Value(T) 13 | case Error(ErrorType) 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | 4 | # Xcode 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | project.xcworkspace/ 10 | xcuserdata/ 11 | # *.json 12 | 13 | # Generated files 14 | *.o 15 | *.pyc 16 | *.hi 17 | 18 | #Python modules 19 | MANIFEST 20 | dist/ 21 | build/ 22 | 23 | # Backup files 24 | *~.nib 25 | \#*# 26 | .#* 27 | 28 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/PersistenceControllerAccessible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistenceControllerAccessible.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 10/20/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | protocol PersistenceControllerAccessible { 10 | var persistenceController: PersistenceController! { get set } 11 | } 12 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 7/28/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(Package) 13 | class Package: NSManagedObject { 14 | 15 | // Insert code here to add functionality to your managed object subclass 16 | 17 | } 18 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/PackageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PackageManager.swift 3 | // PackageTracker 4 | // 5 | // Created by Joe Masilotti on 11/28/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | typealias PackageCompletion = ([String]) -> Void 10 | 11 | protocol PackageManager { 12 | func fetchPackageResults(requestInfo: USPSRequestInfo, completionHandler: PackageCompletion?) 13 | } 14 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/clock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "11-clock@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/disabledClock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "disabledClock@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Package+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package+CoreDataProperties.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 7/28/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | // Delete this file and regenerate it using "Create NSManagedObject Subclass…" 9 | // to keep your implementation up to date with your model. 10 | // 11 | 12 | import Foundation 13 | import CoreData 14 | 15 | extension Package { 16 | 17 | @NSManaged var packageID: String? 18 | @NSManaged var packageNickname: String? 19 | 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PackageTracker 2 | Package tracking app built on Concepts In Code podcast. 3 | 4 | ####Disclosure 5 | The USPS API key used in this app is for demonstration purposes and may not be used in any manner outside of personal use within the PackageTracker app, as discussed on the Concepts In Code podcast. To register for your own API key, please [register for your own](https://www.usps.com/business/web-tools-apis/welcome.htm). Six Five Software, LLC, the owner of the included API key, assumes no liability for use of the API key. Yada, yada, yada...get your own if you are interested. It's free and easy! 6 | -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/MockPackageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPackageManager.swift 3 | // PackageTracker 4 | // 5 | // Created by Joe Masilotti on 11/28/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | @testable import PackageTracker 10 | 11 | class MockPackageManager: PackageManager { 12 | var lastRequestInfo: USPSRequestInfo? 13 | var nextInfos: [String]? 14 | 15 | func fetchPackageResults(requestInfo: USPSRequestInfo, completionHandler: PackageCompletion?) { 16 | lastRequestInfo = requestInfo 17 | 18 | if let infos = nextInfos, completion = completionHandler { 19 | completion(infos) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/PackageModel.xcdatamodeld/PackageModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/PadSplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PadSplitViewController.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 10/20/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PadSplitViewController: UISplitViewController { 12 | func setPersistenceControllerForMasterViewController(pvc: PersistenceController) { 13 | guard let nav = viewControllers.first as? UINavigationController, 14 | var master = nav.topViewController as? PersistenceControllerAccessible else { return } 15 | master.persistenceController = pvc 16 | } 17 | 18 | func fetchPackageInfo(packageID packageID: String) { 19 | guard let detail = viewControllers.last as? ViewController else { return } 20 | detail.fetchPackageInfo(packageID: packageID) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/USPSManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // USPSManager.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 6/16/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct USPSManager: PackageManager { 12 | 13 | func fetchPackageResults(requestInfo: USPSRequestInfo, completionHandler: PackageCompletion?) { 14 | 15 | USPSCommunicator.fetchPackageResults(requestInfo) { (data) -> Void in 16 | let xmlParser = NSXMLParser(data: data) 17 | let packageInfo = Info() 18 | xmlParser.delegate = packageInfo 19 | xmlParser.parse() 20 | let strings = packageInfo.details.map { $0.description } 21 | dispatch_async(dispatch_get_main_queue()) { 22 | completionHandler?(strings) 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ConceptsInCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/USPSCommunicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // USPSCommunicator.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 6/14/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct USPSCommunicator { 12 | 13 | static func fetchPackageResults(requestInfo: USPSRequestInfo, completionHandler: ((data: NSData) -> Void)?) -> () { 14 | guard let url = requestInfo.requestURL else { 15 | return 16 | } 17 | 18 | let session = NSURLSession.sharedSession() 19 | let task = session.dataTaskWithURL(url) { data, response, error in 20 | if let httpResponse = response as? NSHTTPURLResponse { 21 | switch httpResponse.statusCode { 22 | case 200..<300: 23 | print("i'm ok") 24 | guard let data = data else { 25 | break 26 | } 27 | completionHandler?(data: data) 28 | default: 29 | print("i'm not okaaaaaay") 30 | } 31 | } else { 32 | print("not HTTP response. \(error)") 33 | } 34 | } 35 | 36 | task.resume() 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/SampleUSPSXML.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The package is delayed and will not be delivered by the expected delivery date. An updated delivery date will be provided when available. Your item was delivered in or at the mailbox at 1:18 pm on April 22, 2015 in AVON LAKE, OH 44012. 5 | 6 | Arrived at Post Office, April 22, 2015, 5:25 am, AVON LAKE, OH 44012 7 | Arrived at USPS Facility, April 21, 2015, 10:45 pm, CLEVELAND, OH 44101 8 | Out for Delivery, April 20, 2015, 8:38 am, MACEDONIA, OH 44056 9 | Sorting Complete, April 20, 2015, 8:28 am, MACEDONIA, OH 44056 10 | Departed USPS Facility, April 17, 2015, 5:10 am, CITY OF INDUSTRY, CA 91715 11 | Arrived at USPS Origin Facility, April 17, 2015, 4:55 am, CITY OF INDUSTRY, CA 91715 12 | Accepted at USPS Origin Sort Facility, April 17, 2015, 3:40 am, POMONA, CA 91768 13 | Pre-Shipment Info Sent to USPS, April 17, 2015 14 | 15 | -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/USPSRequestInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // USPSRequestInfo.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 5/25/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct USPSRequestInfo { 12 | let userID: String 13 | let packageID: String 14 | 15 | var baseURLString: String { 16 | return "http://production.shippingapis.com" 17 | } 18 | 19 | var requestURL: NSURL? { 20 | let baseURL = NSURL(string: baseURLString)! 21 | return NSURL(string: serializedXML, relativeToURL: baseURL)! 22 | } 23 | 24 | var serializedXML: String { 25 | get { 26 | let urlText = "ShippingAPI.dll?API=TrackV2&XML=" 27 | let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet() 28 | return urlText.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? "" 29 | } 30 | } 31 | 32 | } 33 | 34 | extension USPSRequestInfo: Equatable { } 35 | func ==(lhs: USPSRequestInfo, rhs: USPSRequestInfo) -> Bool { 36 | return lhs.userID == rhs.userID && lhs.packageID == rhs.packageID 37 | } 38 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 4/26/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | let splitViewController = window!.rootViewController as! UISplitViewController 19 | let navController = splitViewController.viewControllers.last as! UINavigationController 20 | navController.topViewController?.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem() 21 | splitViewController.delegate = self 22 | return true 23 | } 24 | 25 | func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool { 26 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 27 | guard let _ = secondaryAsNavController.topViewController as? ViewController else { return false } 28 | return true 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/ViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTests.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 9/12/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PackageTracker 11 | 12 | class ViewControllerTests: XCTestCase { 13 | 14 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 15 | var sut: ViewController! 16 | let packageManager = MockPackageManager() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | sut = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController 22 | sut.packageManager = packageManager 23 | UIApplication.sharedApplication().keyWindow!.rootViewController = sut 24 | XCTAssertNotNil(sut.view) 25 | } 26 | 27 | func testTableViewOutletIsConnected() { 28 | XCTAssertNotNil(sut.tableView) 29 | } 30 | 31 | func test_OnLoad_FetchesPackageDetails() { 32 | sut.fetchPackageInfo(packageID: "package") 33 | 34 | let requestInfo = USPSRequestInfo(userID: "908SIXFI7346", packageID: "package") 35 | XCTAssertEqual(packageManager.lastRequestInfo, requestInfo) 36 | } 37 | 38 | func test_SuccessfulFetch_DisplaysTheResults() { 39 | packageManager.nextInfos = ["Departed", "Transferred", "Arrived"] 40 | 41 | sut.fetchPackageInfo(packageID: "package") 42 | 43 | XCTAssertEqual(sut.tableView(sut.tableView, numberOfRowsInSection: 0), 3) 44 | let firstIndex = NSIndexPath(forRow: 0, inSection: 0) 45 | let firstCell = sut.tableView(sut.tableView, cellForRowAtIndexPath: firstIndex) 46 | XCTAssertEqual(firstCell.textLabel?.text, "Departed") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Info.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Info.swift 3 | // PackageTracker 4 | // 5 | // Created by Hank Turowski on 5/10/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Info: NSObject, NSXMLParserDelegate { 12 | 13 | var id = "" 14 | var summary = Summary() 15 | var details = [Detail]() 16 | 17 | private var parsingContext: NSXMLParserDelegate? 18 | 19 | func parser(parser: NSXMLParser, foundCharacters string: String) { 20 | if let currentContext = parsingContext { 21 | currentContext.parser!(parser, foundCharacters: string) 22 | } 23 | } 24 | 25 | func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI : String?, qualifiedName: String?, attributes attributeDict: [String: String]) { 26 | if elementName == "TrackInfo" { 27 | id = attributeDict["ID"] ?? "" 28 | } else if elementName == "TrackSummary" { 29 | parsingContext = summary 30 | } else if elementName == "TrackDetail" { 31 | details.append(Detail()) 32 | parsingContext = details.last! 33 | } 34 | else if let currentContext = parsingContext { 35 | currentContext.parser!(parser, didStartElement: elementName, namespaceURI: namespaceURI, qualifiedName: qualifiedName, attributes: attributeDict) 36 | } 37 | } 38 | 39 | func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI : String?, qualifiedName: String?) { 40 | if elementName == "TrackSummary" || elementName == "TrackDetail" { 41 | parsingContext = nil 42 | } else if let currentContext = parsingContext { 43 | currentContext.parser!(parser, didEndElement: elementName, namespaceURI: namespaceURI, qualifiedName: qualifiedName) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/TrackingHistoryViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackingHistoryViewControllerTests.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 9/13/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PackageTracker 11 | 12 | class TrackingHistoryViewControllerTests: XCTestCase { 13 | 14 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 15 | var sut: TrackingHistoryViewController! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | sut = storyboard.instantiateViewControllerWithIdentifier("TrackingHistoryViewController") as! TrackingHistoryViewController 21 | XCTAssertNotNil(sut.view) 22 | } 23 | 24 | 25 | func testTableViewOutletIsConnected() { 26 | XCTAssertNotNil(sut.tableView) 27 | } 28 | 29 | func testTrackPackageButtonShouldBeConnected() { 30 | XCTAssertNotNil(sut.trackPackageButton) 31 | } 32 | 33 | func testTrackPackageButtonLabel() { 34 | XCTAssertEqual(sut.trackPackageButton.titleLabel?.text, "Track Package") 35 | } 36 | 37 | func testTrackPackageTextFieldShouldBeConnected() { 38 | XCTAssertNotNil(sut.trackPackageTextField) 39 | } 40 | 41 | func testTableViewDelegateAndDataSourceAreConnected() { 42 | XCTAssertNotNil(sut.tableView.dataSource) 43 | XCTAssertNotNil(sut.tableView.delegate) 44 | } 45 | 46 | func testItemsPropertyIsEmptyStringArrayAtFirst() { 47 | XCTAssertEqual(sut.items.count, 0) 48 | } 49 | 50 | // MARK: test buttons contain actions 51 | func testTrackPackageButtonAction() { 52 | let button = sut.trackPackageButton 53 | let actions = button.actionsForTarget(sut, forControlEvent: .TouchUpInside) ?? [] 54 | XCTAssertTrue(actions.contains("trackPackage:")) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Detail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Detail.swift 3 | // PackageTracker 4 | // 5 | // Created by Hank Turowski on 5/10/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Detail: NSObject, NSXMLParserDelegate { 12 | var eventTime = "" 13 | var eventDate = "" 14 | var event = "" 15 | var eventCity = "" 16 | var eventState = "" 17 | var eventZipCode = "" 18 | var eventCountry = "" 19 | var firmName = "" 20 | var name = "" 21 | var authorizedAgent = "" 22 | 23 | private var parsingContext : String? = nil 24 | 25 | func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI : String?, qualifiedName: String?, attributes attributeDict: [String: String]) { 26 | parsingContext = elementName 27 | } 28 | 29 | func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI : String?, qualifiedName: String?) { 30 | parsingContext = nil 31 | } 32 | 33 | func parser(parser: NSXMLParser, foundCharacters: String) { 34 | if let context = parsingContext { 35 | switch context { 36 | case "EventTime": eventTime = foundCharacters 37 | case "EventDate": eventDate = foundCharacters 38 | case "Event": event = foundCharacters 39 | case "EventCity": eventCity = foundCharacters 40 | case "EventState": eventState = foundCharacters 41 | case "EventZipCode": eventZipCode = foundCharacters 42 | case "EventCountry": eventCountry = foundCharacters 43 | case "FirmName": firmName = foundCharacters 44 | case "Name": name = foundCharacters 45 | case "AuthorizedAgent": authorizedAgent = foundCharacters 46 | default: break 47 | } 48 | } 49 | } 50 | 51 | override var description: String { 52 | return "\(eventDate), \(eventTime), \(event)" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Summary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Summary.swift 3 | // PackageTracker 4 | // 5 | // Created by Hank Turowski on 5/10/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Summary: NSObject, NSXMLParserDelegate { 12 | var eventTime = "" 13 | var eventDate = "" 14 | var event = "" 15 | var eventCity = "" 16 | var eventState = "" 17 | var eventZipCode = "" 18 | var eventCountry = "" 19 | var firmName = "" 20 | var name = "" 21 | var authorizedAgent = "" 22 | var deliveryAttributeCode = "" 23 | 24 | 25 | private var parsingContext : String? = nil 26 | 27 | func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI : String?, qualifiedName: String?, attributes attributeDict: [String: String]) { 28 | parsingContext = elementName 29 | } 30 | 31 | func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI : String?, qualifiedName: String?) { 32 | parsingContext = nil 33 | } 34 | 35 | func parser(parser: NSXMLParser, foundCharacters: String) { 36 | if let context = parsingContext { 37 | switch context { 38 | case "EventTime": eventTime = foundCharacters 39 | case "EventDate": eventDate = foundCharacters 40 | case "Event": event = foundCharacters 41 | case "EventCity": eventCity = foundCharacters 42 | case "EventState": eventState = foundCharacters 43 | case "EventZipCode": eventZipCode = foundCharacters 44 | case "EventCountry": eventCountry = foundCharacters 45 | case "FirmName": firmName = foundCharacters 46 | case "Name": name = foundCharacters 47 | case "AuthorizedAgent": authorizedAgent = foundCharacters 48 | case "DeliveryAttributeCode" : deliveryAttributeCode = foundCharacters 49 | default: break 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSAppTransportSecurity 47 | 48 | NSExceptionDomains 49 | 50 | shippingapis.com 51 | 52 | NSIncludesSubdomains 53 | 54 | NSTemporaryExceptionAllowsInsecureHTTPLoads 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/TestInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestInfo.swift 3 | // PackageTracker 4 | // 5 | // Created by Hank Turowski on 5/10/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | @testable 13 | import PackageTracker 14 | 15 | class TestInfo: XCTestCase { 16 | func testInfoCreation() { 17 | let xmlString = "1:18 pmApril 22, 2015Delivered, In/At MailboxAVON LAKEOH44012false015:25 amApril 22, 2015Arrived at Post OfficeAVON LAKEOH44012false10:45 pmApril 21, 2015Arrived at USPS FacilityCLEVELANDOH44101false" 18 | 19 | let data = (xmlString as NSString).dataUsingEncoding(NSUTF8StringEncoding)! 20 | let xmlParser = NSXMLParser(data: data) 21 | let packageInfo = Info() 22 | xmlParser.delegate = packageInfo 23 | xmlParser.parse() 24 | 25 | XCTAssertEqual(packageInfo.id, "9400116901500000000000") 26 | XCTAssertEqual(packageInfo.summary.event, "Delivered, In/At Mailbox") 27 | XCTAssertEqual(packageInfo.details[0].event , "Arrived at Post Office") 28 | XCTAssertEqual(packageInfo.details[1].event , "Arrived at USPS Facility") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 4/26/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | import CoreData 12 | 13 | class ViewController: UIViewController, UITableViewDataSource { 14 | 15 | var items = [String]() 16 | 17 | internal lazy var packageManager: PackageManager = { USPSManager() }() 18 | 19 | @IBOutlet weak var tableView: UITableView! 20 | @IBOutlet weak var trackingTextField: UITextField! 21 | @IBOutlet weak var trackPackageButton: UIButton! 22 | 23 | @IBAction func showHistory(sender: AnyObject?) { 24 | performSegueWithIdentifier("showHistorySegue", sender: nil) 25 | } 26 | 27 | private func parse(data: NSData) -> Info { 28 | let xmlParser = NSXMLParser(data: data) 29 | let packageInfo = Info() 30 | xmlParser.delegate = packageInfo 31 | xmlParser.parse() 32 | 33 | return packageInfo 34 | } 35 | 36 | // MARK: UITableViewDataSource methods 37 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 38 | 39 | guard let cell = tableView.dequeueReusableCellWithIdentifier("PackageStatusLineCell") else { 40 | return UITableViewCell() 41 | } 42 | 43 | cell.textLabel?.text = items[indexPath.row] 44 | 45 | return cell 46 | } 47 | 48 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 49 | return items.count 50 | } 51 | 52 | private func userIDFromJSON() -> String? { 53 | guard let filePath = NSBundle.mainBundle().pathForResource("USPSConfig", ofType: "json") else { return nil } 54 | let data = NSData(contentsOfFile: filePath)! 55 | let json: AnyObject = try! NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) 56 | let userID = json["userID"] as! String 57 | return userID 58 | } 59 | 60 | func fetchPackageInfo(packageID packageID: String, completion: (Void -> Void)? = nil) { 61 | guard let userID = userIDFromJSON() else { 62 | self.items = ["There's nothing to see here"] 63 | self.tableView?.reloadData() 64 | return 65 | } 66 | let requestInfo = USPSRequestInfo(userID: userID, packageID: packageID) 67 | packageManager.fetchPackageResults(requestInfo) { [weak self] items in 68 | defer { self?.tableView.reloadData() } 69 | if items.isEmpty { 70 | self?.items = ["There's nothing to see here"] 71 | } else { 72 | self?.items = items 73 | completion?() 74 | } 75 | 76 | } 77 | } 78 | 79 | } 80 | 81 | extension ViewController : PackageDependencyInjectable { 82 | func updateDetailsForPackageID(packageID: String, completion: Void -> Void) { 83 | fetchPackageInfo(packageID: packageID, completion: completion) 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/PadSplitViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PadSplitViewControllerTests.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 10/20/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PackageTracker 11 | 12 | class PadSplitViewControllerTests: XCTestCase { 13 | 14 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 15 | var sut: PadSplitViewController! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | sut = storyboard.instantiateViewControllerWithIdentifier("PadSplitViewController") as! PadSplitViewController 21 | UIApplication.sharedApplication().keyWindow!.rootViewController = sut 22 | XCTAssertNotNil(sut.view) 23 | } 24 | 25 | // MARK: test initial state 26 | func testNumberOfViewControllersIsInitially2() { 27 | XCTAssertEqual(sut.viewControllers.count, 2) 28 | } 29 | 30 | func testMasterViewControllerRootIsNavController() { 31 | let nav = sut.viewControllers.first 32 | XCTAssertTrue(nav is UINavigationController, "first view controller should be UINavigationController. Is \(sut.viewControllers.first?.classForCoder)") 33 | } 34 | 35 | func testNavControllerTopVCIsInitiallyTrackingHistoryVC() { 36 | let navVC = sut.viewControllers.first as? UINavigationController 37 | let vc = navVC?.topViewController 38 | XCTAssertNotNil(vc) 39 | XCTAssertTrue(vc is TrackingHistoryViewController, "Nav top VC should be Tracking History VC. Is \(vc?.classForCoder)") 40 | } 41 | 42 | func testViewControllerIsDetailVCOniPad() { 43 | let detail = sut.viewControllers.last 44 | XCTAssertTrue(detail is ViewController) 45 | } 46 | 47 | 48 | // MARK: test ability to set PersistenceController dependency to MasterVC 49 | func testSettingPersistenceControllerToMasterViewController() { 50 | guard let nav = sut.viewControllers.first as? UINavigationController, 51 | var master = nav.topViewController as? PersistenceControllerAccessible else { 52 | XCTFail("MasterVC should conform to PersisntenceControllerAccessible protocol.") 53 | return 54 | } 55 | 56 | // given 57 | let expectation = expectationWithDescription("waiting for core data to set up") 58 | 59 | // when 60 | _ = PersistenceController(modelName: "PackageModel", storeType: .InMemory) { pvc in 61 | expectation.fulfill() 62 | self.sut.setPersistenceControllerForMasterViewController(pvc) 63 | XCTAssertNotNil(master.persistenceController) 64 | } 65 | 66 | // then 67 | waitForExpectationsWithTimeout(5.0, handler: nil) 68 | } 69 | 70 | func testAskingSplitViewToFetchAsksMasterToFetchAndClearsItemsArray() { 71 | // given 72 | guard let detail = sut.viewControllers.last as? ViewController else { 73 | XCTFail("should have ref to detail vc") 74 | return 75 | } 76 | detail.items = ["1", "2", "3"] 77 | 78 | // when 79 | sut.fetchPackageInfo(packageID: "") 80 | 81 | // then 82 | XCTAssertEqual(detail.items, []) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PackageTracker/PersistenceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistenceController.swift 3 | // NewCoreDataStack 4 | // 5 | // Created by BJ on 6/4/15. 6 | // Copyright (c) 2015 Six Five Software, LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | 13 | public enum CoreDataStoreType { 14 | case SQLite, InMemory, Binary 15 | 16 | public var storeTypeString: String { 17 | switch self { 18 | case .SQLite: 19 | return NSSQLiteStoreType 20 | case .InMemory: 21 | return NSInMemoryStoreType 22 | case .Binary: 23 | return NSBinaryStoreType 24 | } 25 | } 26 | } 27 | 28 | public struct PersistenceController { 29 | 30 | public let mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) 31 | private let privateContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) 32 | 33 | private let callback: (PersistenceController -> Void)? 34 | 35 | public init(modelName: String, storeType: CoreDataStoreType = .SQLite, callback: (PersistenceController -> Void)? = nil) { 36 | self.callback = callback 37 | 38 | let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd")! 39 | let mom = NSManagedObjectModel(contentsOfURL: modelURL)! 40 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: mom) 41 | 42 | privateContext.persistentStoreCoordinator = coordinator 43 | mainContext.parentContext = privateContext 44 | 45 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { 46 | let psc = self.privateContext.persistentStoreCoordinator! 47 | let options = [NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true, NSSQLitePragmasOption : ["journal_mode" : "DELETE"]] as [NSObject : AnyObject] 48 | 49 | let fileManager = NSFileManager.defaultManager() 50 | let documentsURL = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last! 51 | let storeURL = documentsURL.URLByAppendingPathComponent("\(modelName).sqlite") 52 | 53 | try! psc.addPersistentStoreWithType(storeType.storeTypeString, configuration: nil, URL: storeURL, options: options) 54 | 55 | dispatch_async(dispatch_get_main_queue()) { 56 | callback?(self) 57 | } 58 | } 59 | } 60 | 61 | public func save() { 62 | 63 | if !privateContext.hasChanges && !mainContext.hasChanges { 64 | return 65 | } 66 | 67 | mainContext.performBlockAndWait { 68 | if let _ = try? self.mainContext.save() { 69 | self.privateContext.performBlock { 70 | if let _ = try? self.privateContext.save() { 71 | // success 72 | } 73 | } 74 | } 75 | } 76 | 77 | } 78 | 79 | public func fetchAll(entity entity: String) -> [AnyObject] { 80 | let fetchRequest = NSFetchRequest(entityName: entity) 81 | var result = [AnyObject]() 82 | do { 83 | result = try mainContext.executeFetchRequest(fetchRequest) 84 | } catch { 85 | print("error fetching") 86 | } 87 | return result 88 | } 89 | } 90 | 91 | public struct WorkerContext { 92 | let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) 93 | var parentContext: NSManagedObjectContext? { 94 | return context.parentContext 95 | } 96 | 97 | public init(parent: NSManagedObjectContext) { 98 | context.parentContext = parent 99 | } 100 | 101 | public func insert(entityName: String) -> NSManagedObject { 102 | let entity = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext: context) 103 | return entity 104 | } 105 | 106 | public func delete(object object: NSManagedObject) { 107 | context.deleteObject(object) 108 | } 109 | 110 | public func save(completion: (Void -> Void)? = nil) { 111 | if !context.hasChanges { 112 | return 113 | } 114 | 115 | context.performBlock { 116 | try! self.context.save() 117 | 118 | completion?() 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/TrackingHistoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackingHistoryViewController.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ Miller on 9/13/15. 6 | // Copyright © 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TrackingHistoryViewController: UIViewController { 12 | 13 | // MARK: outlets 14 | @IBOutlet weak var tableView: UITableView! 15 | @IBOutlet weak var trackPackageTextField: UITextField! 16 | @IBOutlet weak var trackPackageButton: UIButton! 17 | 18 | // dependencies 19 | lazy var persistenceController: PersistenceController = PersistenceController(modelName: "PackageModel", storeType: .SQLite) { pvc in 20 | print("persistence controller created") 21 | NSNotificationCenter.defaultCenter().postNotificationName("core data stack created", object: nil) 22 | } 23 | 24 | var handleSelection: ((String) -> Void)? 25 | 26 | // data source 27 | var items = [Package]() 28 | 29 | var selectedPackageID: String? 30 | 31 | // MARK: view lifecycle methods 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | fetchAndDisplayHistory() 36 | } 37 | 38 | override func viewWillAppear(animated: Bool) { 39 | super.viewWillAppear(animated) 40 | NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("fetchAndDisplayHistory"), name: "core data stack created", object: nil) 41 | } 42 | 43 | override func viewWillDisappear(animated: Bool) { 44 | NSNotificationCenter.defaultCenter().removeObserver(self, name: "core data stack created", object: nil) 45 | } 46 | 47 | 48 | // MARK: actions 49 | @IBAction func trackPackage(sender: AnyObject?) { 50 | selectedPackageID = trackPackageTextField.text 51 | 52 | performSegueWithIdentifier("showDetail", sender: nil) 53 | } 54 | 55 | func fetchAndDisplayHistory() { 56 | guard let items = persistenceController.fetchAll(entity: "Package") as? [Package] else { return } 57 | self.items = items 58 | dispatch_async(dispatch_get_main_queue()) { 59 | self.tableView.reloadData() 60 | } 61 | } 62 | 63 | func persistPackage(packageID: String) { 64 | let workerContext = WorkerContext(parent: self.persistenceController.mainContext) 65 | let package = workerContext.insert("Package") as! Package 66 | package.packageID = packageID 67 | workerContext.save { 68 | self.fetchAndDisplayHistory() 69 | self.persistenceController.save() 70 | } 71 | } 72 | 73 | // MARK: navigation 74 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 75 | if segue.identifier == "showDetail", 76 | let navVC = segue.destinationViewController as? UINavigationController, 77 | packageInjectable = navVC.topViewController as? PackageDependencyInjectable { 78 | 79 | guard let packageID = selectedPackageID else { return } 80 | 81 | packageInjectable.updateDetailsForPackageID(packageID) { self.persistPackage(packageID) } 82 | 83 | let vc = navVC.topViewController 84 | vc?.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem() 85 | vc?.navigationItem.leftItemsSupplementBackButton = true 86 | } 87 | } 88 | 89 | } 90 | 91 | extension TrackingHistoryViewController : UITableViewDataSource, UITableViewDelegate { 92 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 93 | return items.count 94 | } 95 | 96 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 97 | guard let cell = tableView.dequeueReusableCellWithIdentifier("HistoryCell") else { return UITableViewCell() } 98 | cell.textLabel?.text = items[indexPath.row].packageNickname 99 | cell.detailTextLabel?.text = items[indexPath.row].packageID 100 | return cell 101 | } 102 | 103 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 104 | tableView.deselectRowAtIndexPath(indexPath, animated: true) 105 | selectedPackageID = items[indexPath.row].packageID 106 | performSegueWithIdentifier("showDetail", sender: nil) 107 | } 108 | } 109 | 110 | protocol PackageDependencyInjectable { 111 | func updateDetailsForPackageID(packageID: String, completion: Void -> Void) 112 | } -------------------------------------------------------------------------------- /PackageTracker/PackageTrackerTests/USPSRequestFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLRequestCreationTests.swift 3 | // PackageTracker 4 | // 5 | // Created by BJ on 5/25/15. 6 | // Copyright (c) 2015 Concepts In Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | @testable 13 | import PackageTracker 14 | 15 | class USPSRequestFlow: XCTestCase { 16 | 17 | func testProvidingRequestInfoReturnsSerializedXML() { 18 | // given 19 | 20 | let userID = "conceptsincode" 21 | let packageID = "12345" 22 | 23 | let uspsRequestInfo = USPSRequestInfo(userID: userID, packageID: packageID) 24 | 25 | let expectedXMLString = "ShippingAPI.dll?API=TrackV2&XML=%3C?xml%20version=%221.0%22%20encoding=%22UTF%E2%80%908%22%20?%3E%3CTrackFieldRequest%20USERID=%22conceptsincode%22%3E%3CTrackID%20ID=%2212345%22%3E%3C/TrackID%3E%3C/TrackFieldRequest%3E" 26 | 27 | // when 28 | let resultXMLString = uspsRequestInfo.serializedXML 29 | 30 | // then 31 | XCTAssertEqual(resultXMLString, expectedXMLString, "XML strings should be equal") 32 | } 33 | 34 | func testInfoCreation() { 35 | let xmlString = "1:18 pmApril 22, 2015Delivered, In/At MailboxAVON LAKEOH44012false015:25 amApril 22, 2015Arrived at Post OfficeAVON LAKEOH44012false10:45 pmApril 21, 2015Arrived at USPS FacilityCLEVELANDOH44101false" 36 | 37 | let data = (xmlString as NSString).dataUsingEncoding(NSUTF8StringEncoding)! 38 | let xmlParser = NSXMLParser(data: data) 39 | let packageInfo = Info() 40 | xmlParser.delegate = packageInfo 41 | xmlParser.parse() 42 | 43 | XCTAssertEqual(packageInfo.id, "9400116901500000000000") 44 | XCTAssertEqual(packageInfo.summary.event, "Delivered, In/At Mailbox") 45 | XCTAssertEqual(packageInfo.details[0].event , "Arrived at Post Office") 46 | XCTAssertEqual(packageInfo.details[1].event , "Arrived at USPS Facility") 47 | } 48 | 49 | func testFullUSPSURLCreation() { 50 | let userID = "conceptsincode" 51 | let packageID = "12345" 52 | 53 | let expectedURLString = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3C?xml%20version=%221.0%22%20encoding=%22UTF%E2%80%908%22%20?%3E%3CTrackFieldRequest%20USERID=%22conceptsincode%22%3E%3CTrackID%20ID=%2212345%22%3E%3C/TrackID%3E%3C/TrackFieldRequest%3E" 54 | 55 | let request = USPSRequestInfo(userID: userID, packageID: packageID) 56 | guard let url = request.requestURL else { 57 | XCTFail("url should not be nil") 58 | return 59 | } 60 | 61 | XCTAssertEqual(url.absoluteString ?? "", expectedURLString) 62 | } 63 | 64 | // this test is failing because the package information no longer exists on the server. we need to mock the response and test it that way, versus testing against a network API asynchronously. 65 | /* 66 | func testDataGetsReceived() { 67 | let filePath = NSBundle.mainBundle().pathForResource("USPSConfig", ofType: "json")! 68 | let data = NSData(contentsOfFile: filePath)! 69 | var json: [String : String]! 70 | do { 71 | json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as! [String : String] 72 | } catch { 73 | XCTFail("failed with error \(error)") 74 | } 75 | 76 | guard let userID = json["userID"], packageID = json["packageID"] else { 77 | XCTFail("userID and packageID should be gotten from json") 78 | return 79 | } 80 | 81 | let request = USPSRequestInfo(userID: userID, packageID: packageID) 82 | let expectation = expectationWithDescription("testing getting data") 83 | 84 | USPSManager().fetchPackageResults(request) { (items: [String]) -> Void in 85 | XCTAssertGreaterThan(items.count, 0) 86 | expectation.fulfill() 87 | } 88 | 89 | waitForExpectationsWithTimeout(8.0) { (error: NSError?) -> Void in 90 | print("failed with error: \(error?.localizedDescription ?? String())") 91 | } 92 | } 93 | */ 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker.xcodeproj/xcshareddata/xcschemes/PackageTracker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 57 | 58 | 60 | 61 | 62 | 63 | 65 | 71 | 72 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 96 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 115 | 117 | 123 | 124 | 125 | 126 | 128 | 129 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 136 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 172 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /PackageTracker/PackageTracker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 20182FF51B136410002824F4 /* USPSConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 20182FF41B136410002824F4 /* USPSConfig.json */; }; 11 | 20182FF81B1364B4002824F4 /* USPSRequestFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20182FF71B1364B4002824F4 /* USPSRequestFlow.swift */; }; 12 | 20182FFA1B1365F0002824F4 /* USPSRequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20182FF91B1365F0002824F4 /* USPSRequestInfo.swift */; }; 13 | 2029545B1B3119A400F96975 /* USPSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2029545A1B3119A400F96975 /* USPSManager.swift */; }; 14 | 204D48A21B21C62900E173AB /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 204D48A11B21C62900E173AB /* Result.swift */; }; 15 | 205AE8131AED794D00289F3F /* SampleUSPSXML.xml in Resources */ = {isa = PBXBuildFile; fileRef = 205AE8121AED794D00289F3F /* SampleUSPSXML.xml */; }; 16 | 205AE8141AED794D00289F3F /* SampleUSPSXML.xml in Resources */ = {isa = PBXBuildFile; fileRef = 205AE8121AED794D00289F3F /* SampleUSPSXML.xml */; }; 17 | 205EF4B81B2DF6CA0019FE77 /* USPSCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205EF4B71B2DF6CA0019FE77 /* USPSCommunicator.swift */; }; 18 | 207D4D3E1BD67AAB006FAB09 /* PadSplitViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207D4D3D1BD67AAB006FAB09 /* PadSplitViewControllerTests.swift */; settings = {ASSET_TAGS = (); }; }; 19 | 207D4D401BD67AD5006FAB09 /* PadSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207D4D3F1BD67AD5006FAB09 /* PadSplitViewController.swift */; settings = {ASSET_TAGS = (); }; }; 20 | 207D4D421BD67E62006FAB09 /* PersistenceControllerAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207D4D411BD67E62006FAB09 /* PersistenceControllerAccessible.swift */; settings = {ASSET_TAGS = (); }; }; 21 | 2092AAF11B68613E005DBC46 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2092AAF01B68613E005DBC46 /* PersistenceController.swift */; }; 22 | 2092AB151B686623005DBC46 /* PackageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2092AB131B686623005DBC46 /* PackageModel.xcdatamodeld */; }; 23 | 2092AB201B6873F1005DBC46 /* Package+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2092AB1E1B6873F1005DBC46 /* Package+CoreDataProperties.swift */; }; 24 | 2092AB211B6873F1005DBC46 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2092AB1F1B6873F1005DBC46 /* Package.swift */; }; 25 | 20C269621BA510AD0044FCA9 /* ViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C269611BA510AD0044FCA9 /* ViewControllerTests.swift */; settings = {ASSET_TAGS = (); }; }; 26 | 20C269641BA6054D0044FCA9 /* TrackingHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C269631BA6054D0044FCA9 /* TrackingHistoryViewController.swift */; settings = {ASSET_TAGS = (); }; }; 27 | 20C269661BA60B1B0044FCA9 /* TrackingHistoryViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C269651BA60B1B0044FCA9 /* TrackingHistoryViewControllerTests.swift */; settings = {ASSET_TAGS = (); }; }; 28 | 20DE6D891AED687E00A7A73F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DE6D881AED687E00A7A73F /* AppDelegate.swift */; }; 29 | 20DE6D8B1AED687E00A7A73F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DE6D8A1AED687E00A7A73F /* ViewController.swift */; }; 30 | 20DE6D8E1AED687E00A7A73F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 20DE6D8C1AED687E00A7A73F /* Main.storyboard */; }; 31 | 20DE6D901AED687E00A7A73F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 20DE6D8F1AED687E00A7A73F /* Images.xcassets */; }; 32 | 20DE6D931AED687E00A7A73F /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 20DE6D911AED687E00A7A73F /* LaunchScreen.xib */; }; 33 | 55476F5B1AFFD34C00F9003C /* Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55476F5A1AFFD34C00F9003C /* Summary.swift */; }; 34 | 55476F5E1AFFD57300F9003C /* Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55476F5D1AFFD57300F9003C /* Detail.swift */; }; 35 | 55476F611AFFD5ED00F9003C /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55476F601AFFD5ED00F9003C /* Info.swift */; }; 36 | 55476F671AFFED4B00F9003C /* TestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55476F661AFFED4B00F9003C /* TestInfo.swift */; }; 37 | 84F91D511C0A2326005FBFCE /* PackageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F91D501C0A2326005FBFCE /* PackageManager.swift */; }; 38 | 84F91D531C0A246B005FBFCE /* MockPackageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F91D521C0A246B005FBFCE /* MockPackageManager.swift */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXContainerItemProxy section */ 42 | 20DE6D991AED687E00A7A73F /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 20DE6D7B1AED687E00A7A73F /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 20DE6D821AED687E00A7A73F; 47 | remoteInfo = PackageTracker; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 20182FF41B136410002824F4 /* USPSConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = USPSConfig.json; sourceTree = ""; }; 53 | 20182FF71B1364B4002824F4 /* USPSRequestFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = USPSRequestFlow.swift; sourceTree = ""; }; 54 | 20182FF91B1365F0002824F4 /* USPSRequestInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = USPSRequestInfo.swift; path = PackageTrackerTests/USPSRequestInfo.swift; sourceTree = SOURCE_ROOT; }; 55 | 2029545A1B3119A400F96975 /* USPSManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = USPSManager.swift; sourceTree = ""; }; 56 | 204D48A11B21C62900E173AB /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 57 | 205AE8121AED794D00289F3F /* SampleUSPSXML.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = SampleUSPSXML.xml; sourceTree = ""; }; 58 | 205EF4B71B2DF6CA0019FE77 /* USPSCommunicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = USPSCommunicator.swift; sourceTree = ""; }; 59 | 207D4D3D1BD67AAB006FAB09 /* PadSplitViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PadSplitViewControllerTests.swift; sourceTree = ""; }; 60 | 207D4D3F1BD67AD5006FAB09 /* PadSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PadSplitViewController.swift; sourceTree = ""; }; 61 | 207D4D411BD67E62006FAB09 /* PersistenceControllerAccessible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistenceControllerAccessible.swift; sourceTree = ""; }; 62 | 2092AAF01B68613E005DBC46 /* PersistenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PersistenceController.swift; path = ../PersistenceController.swift; sourceTree = ""; }; 63 | 2092AB141B686623005DBC46 /* PackageModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = PackageModel.xcdatamodel; sourceTree = ""; }; 64 | 2092AB1E1B6873F1005DBC46 /* Package+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Package+CoreDataProperties.swift"; sourceTree = ""; }; 65 | 2092AB1F1B6873F1005DBC46 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 66 | 20C269611BA510AD0044FCA9 /* ViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerTests.swift; sourceTree = ""; }; 67 | 20C269631BA6054D0044FCA9 /* TrackingHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingHistoryViewController.swift; sourceTree = ""; }; 68 | 20C269651BA60B1B0044FCA9 /* TrackingHistoryViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingHistoryViewControllerTests.swift; sourceTree = ""; }; 69 | 20DE6D831AED687E00A7A73F /* PackageTracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PackageTracker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 20DE6D871AED687E00A7A73F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 20DE6D881AED687E00A7A73F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 72 | 20DE6D8A1AED687E00A7A73F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 73 | 20DE6D8D1AED687E00A7A73F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 74 | 20DE6D8F1AED687E00A7A73F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 75 | 20DE6D921AED687E00A7A73F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 76 | 20DE6D981AED687E00A7A73F /* PackageTrackerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PackageTrackerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | 20DE6D9D1AED687E00A7A73F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | 55476F5A1AFFD34C00F9003C /* Summary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Summary.swift; sourceTree = ""; }; 79 | 55476F5D1AFFD57300F9003C /* Detail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Detail.swift; sourceTree = ""; }; 80 | 55476F601AFFD5ED00F9003C /* Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = ""; }; 81 | 55476F661AFFED4B00F9003C /* TestInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestInfo.swift; sourceTree = ""; }; 82 | 84F91D501C0A2326005FBFCE /* PackageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PackageManager.swift; sourceTree = ""; }; 83 | 84F91D521C0A246B005FBFCE /* MockPackageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPackageManager.swift; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 20DE6D801AED687E00A7A73F /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | 20DE6D951AED687E00A7A73F /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 20DE6D7A1AED687E00A7A73F = { 105 | isa = PBXGroup; 106 | children = ( 107 | 20DE6D851AED687E00A7A73F /* PackageTracker */, 108 | 20DE6D9B1AED687E00A7A73F /* PackageTrackerTests */, 109 | 20DE6D841AED687E00A7A73F /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | 20DE6D841AED687E00A7A73F /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 20DE6D831AED687E00A7A73F /* PackageTracker.app */, 117 | 20DE6D981AED687E00A7A73F /* PackageTrackerTests.xctest */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | 20DE6D851AED687E00A7A73F /* PackageTracker */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 207D4D3F1BD67AD5006FAB09 /* PadSplitViewController.swift */, 126 | 207D4D411BD67E62006FAB09 /* PersistenceControllerAccessible.swift */, 127 | 20C269631BA6054D0044FCA9 /* TrackingHistoryViewController.swift */, 128 | 2092AAF01B68613E005DBC46 /* PersistenceController.swift */, 129 | 205EF4B71B2DF6CA0019FE77 /* USPSCommunicator.swift */, 130 | 2029545A1B3119A400F96975 /* USPSManager.swift */, 131 | 84F91D501C0A2326005FBFCE /* PackageManager.swift */, 132 | 55476F591AFFD2E200F9003C /* Model */, 133 | 20182FF91B1365F0002824F4 /* USPSRequestInfo.swift */, 134 | 20DE6D881AED687E00A7A73F /* AppDelegate.swift */, 135 | 20DE6D8A1AED687E00A7A73F /* ViewController.swift */, 136 | 204D48A11B21C62900E173AB /* Result.swift */, 137 | 20DE6D8C1AED687E00A7A73F /* Main.storyboard */, 138 | 20DE6D8F1AED687E00A7A73F /* Images.xcassets */, 139 | 20DE6D911AED687E00A7A73F /* LaunchScreen.xib */, 140 | 20DE6D861AED687E00A7A73F /* Supporting Files */, 141 | ); 142 | path = PackageTracker; 143 | sourceTree = ""; 144 | }; 145 | 20DE6D861AED687E00A7A73F /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 205AE8121AED794D00289F3F /* SampleUSPSXML.xml */, 149 | 20DE6D871AED687E00A7A73F /* Info.plist */, 150 | 20182FF41B136410002824F4 /* USPSConfig.json */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 20DE6D9B1AED687E00A7A73F /* PackageTrackerTests */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 207D4D3D1BD67AAB006FAB09 /* PadSplitViewControllerTests.swift */, 159 | 20DE6D9C1AED687E00A7A73F /* Supporting Files */, 160 | 55476F661AFFED4B00F9003C /* TestInfo.swift */, 161 | 20182FF71B1364B4002824F4 /* USPSRequestFlow.swift */, 162 | 20C269611BA510AD0044FCA9 /* ViewControllerTests.swift */, 163 | 20C269651BA60B1B0044FCA9 /* TrackingHistoryViewControllerTests.swift */, 164 | 84F91D521C0A246B005FBFCE /* MockPackageManager.swift */, 165 | ); 166 | path = PackageTrackerTests; 167 | sourceTree = ""; 168 | }; 169 | 20DE6D9C1AED687E00A7A73F /* Supporting Files */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 20DE6D9D1AED687E00A7A73F /* Info.plist */, 173 | ); 174 | name = "Supporting Files"; 175 | sourceTree = ""; 176 | }; 177 | 55476F591AFFD2E200F9003C /* Model */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 2092AB1E1B6873F1005DBC46 /* Package+CoreDataProperties.swift */, 181 | 2092AB1F1B6873F1005DBC46 /* Package.swift */, 182 | 2092AB131B686623005DBC46 /* PackageModel.xcdatamodeld */, 183 | 55476F5A1AFFD34C00F9003C /* Summary.swift */, 184 | 55476F5D1AFFD57300F9003C /* Detail.swift */, 185 | 55476F601AFFD5ED00F9003C /* Info.swift */, 186 | ); 187 | name = Model; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXGroup section */ 191 | 192 | /* Begin PBXNativeTarget section */ 193 | 20DE6D821AED687E00A7A73F /* PackageTracker */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 20DE6DA21AED687E00A7A73F /* Build configuration list for PBXNativeTarget "PackageTracker" */; 196 | buildPhases = ( 197 | 20DE6D7F1AED687E00A7A73F /* Sources */, 198 | 20DE6D801AED687E00A7A73F /* Frameworks */, 199 | 20DE6D811AED687E00A7A73F /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | ); 205 | name = PackageTracker; 206 | productName = PackageTracker; 207 | productReference = 20DE6D831AED687E00A7A73F /* PackageTracker.app */; 208 | productType = "com.apple.product-type.application"; 209 | }; 210 | 20DE6D971AED687E00A7A73F /* PackageTrackerTests */ = { 211 | isa = PBXNativeTarget; 212 | buildConfigurationList = 20DE6DA51AED687E00A7A73F /* Build configuration list for PBXNativeTarget "PackageTrackerTests" */; 213 | buildPhases = ( 214 | 20DE6D941AED687E00A7A73F /* Sources */, 215 | 20DE6D951AED687E00A7A73F /* Frameworks */, 216 | 20DE6D961AED687E00A7A73F /* Resources */, 217 | ); 218 | buildRules = ( 219 | ); 220 | dependencies = ( 221 | 20DE6D9A1AED687E00A7A73F /* PBXTargetDependency */, 222 | ); 223 | name = PackageTrackerTests; 224 | productName = PackageTrackerTests; 225 | productReference = 20DE6D981AED687E00A7A73F /* PackageTrackerTests.xctest */; 226 | productType = "com.apple.product-type.bundle.unit-test"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | 20DE6D7B1AED687E00A7A73F /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastSwiftUpdateCheck = 0700; 235 | LastUpgradeCheck = 0700; 236 | ORGANIZATIONNAME = "Concepts In Code"; 237 | TargetAttributes = { 238 | 20DE6D821AED687E00A7A73F = { 239 | CreatedOnToolsVersion = 6.3.1; 240 | }; 241 | 20DE6D971AED687E00A7A73F = { 242 | CreatedOnToolsVersion = 6.3.1; 243 | TestTargetID = 20DE6D821AED687E00A7A73F; 244 | }; 245 | }; 246 | }; 247 | buildConfigurationList = 20DE6D7E1AED687E00A7A73F /* Build configuration list for PBXProject "PackageTracker" */; 248 | compatibilityVersion = "Xcode 3.2"; 249 | developmentRegion = English; 250 | hasScannedForEncodings = 0; 251 | knownRegions = ( 252 | en, 253 | Base, 254 | ); 255 | mainGroup = 20DE6D7A1AED687E00A7A73F; 256 | productRefGroup = 20DE6D841AED687E00A7A73F /* Products */; 257 | projectDirPath = ""; 258 | projectRoot = ""; 259 | targets = ( 260 | 20DE6D821AED687E00A7A73F /* PackageTracker */, 261 | 20DE6D971AED687E00A7A73F /* PackageTrackerTests */, 262 | ); 263 | }; 264 | /* End PBXProject section */ 265 | 266 | /* Begin PBXResourcesBuildPhase section */ 267 | 20DE6D811AED687E00A7A73F /* Resources */ = { 268 | isa = PBXResourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 20DE6D8E1AED687E00A7A73F /* Main.storyboard in Resources */, 272 | 20DE6D931AED687E00A7A73F /* LaunchScreen.xib in Resources */, 273 | 205AE8131AED794D00289F3F /* SampleUSPSXML.xml in Resources */, 274 | 20DE6D901AED687E00A7A73F /* Images.xcassets in Resources */, 275 | 20182FF51B136410002824F4 /* USPSConfig.json in Resources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 20DE6D961AED687E00A7A73F /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 205AE8141AED794D00289F3F /* SampleUSPSXML.xml in Resources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXResourcesBuildPhase section */ 288 | 289 | /* Begin PBXSourcesBuildPhase section */ 290 | 20DE6D7F1AED687E00A7A73F /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 84F91D511C0A2326005FBFCE /* PackageManager.swift in Sources */, 295 | 2092AB201B6873F1005DBC46 /* Package+CoreDataProperties.swift in Sources */, 296 | 20182FFA1B1365F0002824F4 /* USPSRequestInfo.swift in Sources */, 297 | 55476F5E1AFFD57300F9003C /* Detail.swift in Sources */, 298 | 207D4D401BD67AD5006FAB09 /* PadSplitViewController.swift in Sources */, 299 | 55476F5B1AFFD34C00F9003C /* Summary.swift in Sources */, 300 | 20DE6D8B1AED687E00A7A73F /* ViewController.swift in Sources */, 301 | 2029545B1B3119A400F96975 /* USPSManager.swift in Sources */, 302 | 207D4D421BD67E62006FAB09 /* PersistenceControllerAccessible.swift in Sources */, 303 | 204D48A21B21C62900E173AB /* Result.swift in Sources */, 304 | 205EF4B81B2DF6CA0019FE77 /* USPSCommunicator.swift in Sources */, 305 | 20C269641BA6054D0044FCA9 /* TrackingHistoryViewController.swift in Sources */, 306 | 55476F611AFFD5ED00F9003C /* Info.swift in Sources */, 307 | 20DE6D891AED687E00A7A73F /* AppDelegate.swift in Sources */, 308 | 2092AAF11B68613E005DBC46 /* PersistenceController.swift in Sources */, 309 | 2092AB151B686623005DBC46 /* PackageModel.xcdatamodeld in Sources */, 310 | 2092AB211B6873F1005DBC46 /* Package.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | 20DE6D941AED687E00A7A73F /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | 84F91D531C0A246B005FBFCE /* MockPackageManager.swift in Sources */, 319 | 20C269621BA510AD0044FCA9 /* ViewControllerTests.swift in Sources */, 320 | 20182FF81B1364B4002824F4 /* USPSRequestFlow.swift in Sources */, 321 | 207D4D3E1BD67AAB006FAB09 /* PadSplitViewControllerTests.swift in Sources */, 322 | 20C269661BA60B1B0044FCA9 /* TrackingHistoryViewControllerTests.swift in Sources */, 323 | 55476F671AFFED4B00F9003C /* TestInfo.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXTargetDependency section */ 330 | 20DE6D9A1AED687E00A7A73F /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | target = 20DE6D821AED687E00A7A73F /* PackageTracker */; 333 | targetProxy = 20DE6D991AED687E00A7A73F /* PBXContainerItemProxy */; 334 | }; 335 | /* End PBXTargetDependency section */ 336 | 337 | /* Begin PBXVariantGroup section */ 338 | 20DE6D8C1AED687E00A7A73F /* Main.storyboard */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | 20DE6D8D1AED687E00A7A73F /* Base */, 342 | ); 343 | name = Main.storyboard; 344 | sourceTree = ""; 345 | }; 346 | 20DE6D911AED687E00A7A73F /* LaunchScreen.xib */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | 20DE6D921AED687E00A7A73F /* Base */, 350 | ); 351 | name = LaunchScreen.xib; 352 | sourceTree = ""; 353 | }; 354 | /* End PBXVariantGroup section */ 355 | 356 | /* Begin XCBuildConfiguration section */ 357 | 20DE6DA01AED687E00A7A73F /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 362 | CLANG_CXX_LIBRARY = "libc++"; 363 | CLANG_ENABLE_MODULES = YES; 364 | CLANG_ENABLE_OBJC_ARC = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_CONSTANT_CONVERSION = YES; 367 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 368 | CLANG_WARN_EMPTY_BODY = YES; 369 | CLANG_WARN_ENUM_CONVERSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 395 | MTL_ENABLE_DEBUG_INFO = YES; 396 | ONLY_ACTIVE_ARCH = YES; 397 | SDKROOT = iphoneos; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | }; 401 | name = Debug; 402 | }; 403 | 20DE6DA11AED687E00A7A73F /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_WARN_BOOL_CONVERSION = YES; 412 | CLANG_WARN_CONSTANT_CONVERSION = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_EMPTY_BODY = YES; 415 | CLANG_WARN_ENUM_CONVERSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 423 | ENABLE_NS_ASSERTIONS = NO; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu99; 426 | GCC_NO_COMMON_BLOCKS = YES; 427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 428 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 429 | GCC_WARN_UNDECLARED_SELECTOR = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 431 | GCC_WARN_UNUSED_FUNCTION = YES; 432 | GCC_WARN_UNUSED_VARIABLE = YES; 433 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 434 | MTL_ENABLE_DEBUG_INFO = NO; 435 | SDKROOT = iphoneos; 436 | TARGETED_DEVICE_FAMILY = "1,2"; 437 | VALIDATE_PRODUCT = YES; 438 | }; 439 | name = Release; 440 | }; 441 | 20DE6DA31AED687E00A7A73F /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | INFOPLIST_FILE = PackageTracker/Info.plist; 446 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 448 | PRODUCT_BUNDLE_IDENTIFIER = "com.conceptsincode.$(PRODUCT_NAME:rfc1034identifier)"; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | }; 451 | name = Debug; 452 | }; 453 | 20DE6DA41AED687E00A7A73F /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | INFOPLIST_FILE = PackageTracker/Info.plist; 458 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = "com.conceptsincode.$(PRODUCT_NAME:rfc1034identifier)"; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | }; 463 | name = Release; 464 | }; 465 | 20DE6DA61AED687E00A7A73F /* Debug */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | BUNDLE_LOADER = "$(TEST_HOST)"; 469 | FRAMEWORK_SEARCH_PATHS = ( 470 | "$(SDKROOT)/Developer/Library/Frameworks", 471 | "$(inherited)", 472 | ); 473 | GCC_PREPROCESSOR_DEFINITIONS = ( 474 | "DEBUG=1", 475 | "$(inherited)", 476 | ); 477 | INFOPLIST_FILE = PackageTrackerTests/Info.plist; 478 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 479 | PRODUCT_BUNDLE_IDENTIFIER = "com.conceptsincode.$(PRODUCT_NAME:rfc1034identifier)"; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PackageTracker.app/PackageTracker"; 482 | }; 483 | name = Debug; 484 | }; 485 | 20DE6DA71AED687E00A7A73F /* Release */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | BUNDLE_LOADER = "$(TEST_HOST)"; 489 | FRAMEWORK_SEARCH_PATHS = ( 490 | "$(SDKROOT)/Developer/Library/Frameworks", 491 | "$(inherited)", 492 | ); 493 | INFOPLIST_FILE = PackageTrackerTests/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = "com.conceptsincode.$(PRODUCT_NAME:rfc1034identifier)"; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PackageTracker.app/PackageTracker"; 498 | }; 499 | name = Release; 500 | }; 501 | /* End XCBuildConfiguration section */ 502 | 503 | /* Begin XCConfigurationList section */ 504 | 20DE6D7E1AED687E00A7A73F /* Build configuration list for PBXProject "PackageTracker" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 20DE6DA01AED687E00A7A73F /* Debug */, 508 | 20DE6DA11AED687E00A7A73F /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | 20DE6DA21AED687E00A7A73F /* Build configuration list for PBXNativeTarget "PackageTracker" */ = { 514 | isa = XCConfigurationList; 515 | buildConfigurations = ( 516 | 20DE6DA31AED687E00A7A73F /* Debug */, 517 | 20DE6DA41AED687E00A7A73F /* Release */, 518 | ); 519 | defaultConfigurationIsVisible = 0; 520 | defaultConfigurationName = Release; 521 | }; 522 | 20DE6DA51AED687E00A7A73F /* Build configuration list for PBXNativeTarget "PackageTrackerTests" */ = { 523 | isa = XCConfigurationList; 524 | buildConfigurations = ( 525 | 20DE6DA61AED687E00A7A73F /* Debug */, 526 | 20DE6DA71AED687E00A7A73F /* Release */, 527 | ); 528 | defaultConfigurationIsVisible = 0; 529 | defaultConfigurationName = Release; 530 | }; 531 | /* End XCConfigurationList section */ 532 | 533 | /* Begin XCVersionGroup section */ 534 | 2092AB131B686623005DBC46 /* PackageModel.xcdatamodeld */ = { 535 | isa = XCVersionGroup; 536 | children = ( 537 | 2092AB141B686623005DBC46 /* PackageModel.xcdatamodel */, 538 | ); 539 | currentVersion = 2092AB141B686623005DBC46 /* PackageModel.xcdatamodel */; 540 | path = PackageModel.xcdatamodeld; 541 | sourceTree = ""; 542 | versionGroupType = wrapper.xcdatamodel; 543 | }; 544 | /* End XCVersionGroup section */ 545 | }; 546 | rootObject = 20DE6D7B1AED687E00A7A73F /* Project object */; 547 | } 548 | --------------------------------------------------------------------------------