├── CoreDataRemote ├── Resources │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Info.plist │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard ├── Data │ ├── CoreDataStack.swift │ ├── StarWars.xcdatamodeld │ │ └── StarWars.xcdatamodel │ │ │ └── contents │ ├── Film.swift │ └── DataProvider.swift ├── Repository │ └── ApiRepository.swift └── Controllers │ └── FilmListViewController.swift ├── CoreDataRemote.xcodeproj ├── xcuserdata │ └── alfianlosari.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── CoreDataRemote.xcscheme ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj └── README.md /CoreDataRemote/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/xcuserdata/alfianlosari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS App - Fetching Star Wars API Using Core Data Background Context 2 | The iOS app shows an example of fetching remote data from Star Wars API using Core Data Background ManagedObjectContext and merge the results to the View Context that uses NSFetchedResultsController to update the User Interface 3 | 4 | # Requirements 5 | 1. Xcode 9.4+ 6 | 7 | ## Installation 8 | 1. Clone or Download the project 9 | 2. Open in Xcode 10 | 3. Build and Run the project 11 | -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/xcuserdata/alfianlosari.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CoreDataRemote.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8B49D4C121588CDF00803884 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CoreDataRemote/Resources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | var coreDataStack = CoreDataStack.shared 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | let vc = (window?.rootViewController as? UINavigationController)?.topViewController as? FilmListViewController 20 | vc?.dataProvider = DataProvider(persistentContainer: coreDataStack.persistentContainer, repository: ApiRepository.shared) 21 | 22 | return true 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /CoreDataRemote/Data/CoreDataStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStack.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | class CoreDataStack { 12 | 13 | private init() {} 14 | static let shared = CoreDataStack() 15 | 16 | lazy var persistentContainer: NSPersistentContainer = { 17 | let container = NSPersistentContainer(name: "StarWars") 18 | 19 | container.loadPersistentStores(completionHandler: { (_, error) in 20 | guard let error = error as NSError? else { return } 21 | fatalError("Unresolved error: \(error), \(error.userInfo)") 22 | }) 23 | 24 | container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 25 | container.viewContext.undoManager = nil 26 | container.viewContext.shouldDeleteInaccessibleFaults = true 27 | 28 | container.viewContext.automaticallyMergesChangesFromParent = true 29 | 30 | return container 31 | }() 32 | 33 | } 34 | -------------------------------------------------------------------------------- /CoreDataRemote/Data/StarWars.xcdatamodeld/StarWars.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CoreDataRemote/Data/Film.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Film.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | class Film: NSManagedObject { 12 | 13 | @NSManaged var director: String 14 | @NSManaged var episodeId: NSNumber 15 | @NSManaged var openingCrawl: String 16 | @NSManaged var producer: String 17 | @NSManaged var releaseDate: Date 18 | @NSManaged var title: String 19 | 20 | static let dateFormatter: DateFormatter = { 21 | let df = DateFormatter() 22 | df.dateFormat = "YYYY-MM-dd" 23 | return df 24 | }() 25 | 26 | func update(with jsonDictionary: [String: Any]) throws { 27 | guard let director = jsonDictionary["director"] as? String, 28 | let episodeId = jsonDictionary["episode_id"] as? Int, 29 | let openingCrawl = jsonDictionary["opening_crawl"] as? String, 30 | let producer = jsonDictionary["producer"] as? String, 31 | let releaseDate = jsonDictionary["release_date"] as? String, 32 | let title = jsonDictionary["title"] as? String 33 | else { 34 | throw NSError(domain: "", code: 100, userInfo: nil) 35 | } 36 | 37 | self.director = director 38 | self.episodeId = NSNumber(value: episodeId) 39 | self.openingCrawl = openingCrawl 40 | self.producer = producer 41 | self.releaseDate = Film.dateFormatter.date(from: releaseDate) ?? Date(timeIntervalSince1970: 0) 42 | self.title = title 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /CoreDataRemote/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CoreDataRemote/Repository/ApiRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarWarsRepository.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ApiRepository { 12 | 13 | private init() {} 14 | static let shared = ApiRepository() 15 | 16 | private let urlSession = URLSession.shared 17 | private let baseURL = URL(string: "https://swapi.co/api/")! 18 | 19 | func getFilms(completion: @escaping(_ filmsDict: [[String: Any]]?, _ error: Error?) -> ()) { 20 | let filmURL = baseURL.appendingPathComponent("films") 21 | urlSession.dataTask(with: filmURL) { (data, response, error) in 22 | if let error = error { 23 | completion(nil, error) 24 | return 25 | } 26 | 27 | guard let data = data else { 28 | let error = NSError(domain: dataErrorDomain, code: DataErrorCode.networkUnavailable.rawValue, userInfo: nil) 29 | completion(nil, error) 30 | return 31 | } 32 | 33 | do { 34 | let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) 35 | guard let jsonDictionary = jsonObject as? [String: Any], let result = jsonDictionary["results"] as? [[String: Any]] else { 36 | throw NSError(domain: dataErrorDomain, code: DataErrorCode.wrongDataFormat.rawValue, userInfo: nil) 37 | } 38 | completion(result, nil) 39 | } catch { 40 | completion(nil, error) 41 | } 42 | }.resume() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /CoreDataRemote/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CoreDataRemote/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CoreDataRemote/Controllers/FilmListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class FilmListViewController: UITableViewController { 13 | 14 | var dataProvider: DataProvider! 15 | lazy var fetchedResultsController: NSFetchedResultsController = { 16 | let fetchRequest = NSFetchRequest(entityName:"Film") 17 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: "episodeId", ascending:true)] 18 | 19 | let controller = NSFetchedResultsController(fetchRequest: fetchRequest, 20 | managedObjectContext: dataProvider.viewContext, 21 | sectionNameKeyPath: nil, cacheName: nil) 22 | controller.delegate = self 23 | 24 | do { 25 | try controller.performFetch() 26 | } catch { 27 | let nserror = error as NSError 28 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 29 | } 30 | 31 | return controller 32 | }() 33 | 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | dataProvider.fetchFilms { (error) in 38 | // Handle Error by displaying it in UI 39 | } 40 | } 41 | 42 | override func numberOfSections(in tableView: UITableView) -> Int { 43 | return fetchedResultsController.sections?.count ?? 0 44 | } 45 | 46 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 47 | return fetchedResultsController.sections?[section].numberOfObjects ?? 0 48 | } 49 | 50 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 51 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 52 | let film = fetchedResultsController.object(at: indexPath) 53 | cell.textLabel?.text = film.title 54 | cell.detailTextLabel?.text = film.director 55 | return cell 56 | } 57 | 58 | } 59 | 60 | extension FilmListViewController: NSFetchedResultsControllerDelegate { 61 | 62 | func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 63 | tableView.reloadData() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/xcuserdata/alfianlosari.xcuserdatad/xcschemes/CoreDataRemote.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CoreDataRemote/Data/DataProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncCoordinator.swift 3 | // CoreDataRemote 4 | // 5 | // Created by Alfian Losari on 24/09/18. 6 | // Copyright © 2018 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | let dataErrorDomain = "dataErrorDomain" 12 | 13 | enum DataErrorCode: NSInteger { 14 | case networkUnavailable = 101 15 | case wrongDataFormat = 102 16 | } 17 | 18 | class DataProvider { 19 | 20 | private let persistentContainer: NSPersistentContainer 21 | private let repository: ApiRepository 22 | 23 | var viewContext: NSManagedObjectContext { 24 | return persistentContainer.viewContext 25 | } 26 | 27 | init(persistentContainer: NSPersistentContainer, repository: ApiRepository) { 28 | self.persistentContainer = persistentContainer 29 | self.repository = repository 30 | } 31 | 32 | func fetchFilms(completion: @escaping(Error?) -> Void) { 33 | repository.getFilms() { jsonDictionary, error in 34 | if let error = error { 35 | completion(error) 36 | return 37 | } 38 | 39 | guard let jsonDictionary = jsonDictionary else { 40 | let error = NSError(domain: dataErrorDomain, code: DataErrorCode.wrongDataFormat.rawValue, userInfo: nil) 41 | completion(error) 42 | return 43 | } 44 | 45 | let taskContext = self.persistentContainer.newBackgroundContext() 46 | taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 47 | taskContext.undoManager = nil 48 | 49 | _ = self.syncFilms(jsonDictionary: jsonDictionary, taskContext: taskContext) 50 | 51 | completion(nil) 52 | } 53 | } 54 | 55 | private func syncFilms(jsonDictionary: [[String: Any]], taskContext: NSManagedObjectContext) -> Bool { 56 | var successfull = false 57 | taskContext.performAndWait { 58 | let matchingEpisodeRequest = NSFetchRequest(entityName: "Film") 59 | let episodeIds = jsonDictionary.map { $0["episode_id"] as? Int }.compactMap { $0 } 60 | matchingEpisodeRequest.predicate = NSPredicate(format: "episodeId in %@", argumentArray: [episodeIds]) 61 | 62 | let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: matchingEpisodeRequest) 63 | batchDeleteRequest.resultType = .resultTypeObjectIDs 64 | 65 | // Execute the request to de batch delete and merge the changes to viewContext, which triggers the UI update 66 | do { 67 | let batchDeleteResult = try taskContext.execute(batchDeleteRequest) as? NSBatchDeleteResult 68 | 69 | if let deletedObjectIDs = batchDeleteResult?.result as? [NSManagedObjectID] { 70 | NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [NSDeletedObjectsKey: deletedObjectIDs], 71 | into: [self.persistentContainer.viewContext]) 72 | } 73 | } catch { 74 | print("Error: \(error)\nCould not batch delete existing records.") 75 | return 76 | } 77 | 78 | // Create new records. 79 | for filmDictionary in jsonDictionary { 80 | 81 | guard let film = NSEntityDescription.insertNewObject(forEntityName: "Film", into: taskContext) as? Film else { 82 | print("Error: Failed to create a new Film object!") 83 | return 84 | } 85 | 86 | do { 87 | try film.update(with: filmDictionary) 88 | } catch { 89 | print("Error: \(error)\nThe quake object will be deleted.") 90 | taskContext.delete(film) 91 | } 92 | } 93 | 94 | // Save all the changes just made and reset the taskContext to free the cache. 95 | if taskContext.hasChanges { 96 | do { 97 | try taskContext.save() 98 | } catch { 99 | print("Error: \(error)\nCould not save Core Data context.") 100 | } 101 | taskContext.reset() // Reset the context to clean up the cache and low the memory footprint. 102 | } 103 | successfull = true 104 | } 105 | return successfull 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /CoreDataRemote/Resources/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 | 34 | 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 | -------------------------------------------------------------------------------- /CoreDataRemote.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8B49D4C621588CDF00803884 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4C521588CDF00803884 /* AppDelegate.swift */; }; 11 | 8B49D4C821588CDF00803884 /* FilmListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4C721588CDF00803884 /* FilmListViewController.swift */; }; 12 | 8B49D4CB21588CDF00803884 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B49D4C921588CDF00803884 /* Main.storyboard */; }; 13 | 8B49D4CD21588CE000803884 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B49D4CC21588CE000803884 /* Assets.xcassets */; }; 14 | 8B49D4D021588CE000803884 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B49D4CE21588CE000803884 /* LaunchScreen.storyboard */; }; 15 | 8B49D4D8215892EC00803884 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4D7215892EC00803884 /* CoreDataStack.swift */; }; 16 | 8B49D4DA2158A23300803884 /* ApiRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4D92158A23300803884 /* ApiRepository.swift */; }; 17 | 8B49D4DD2158A40B00803884 /* StarWars.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4DB2158A40B00803884 /* StarWars.xcdatamodeld */; }; 18 | 8B49D4DF2158A57D00803884 /* Film.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4DE2158A57D00803884 /* Film.swift */; }; 19 | 8B49D4E12158B20D00803884 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B49D4E02158B20D00803884 /* DataProvider.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 8B49D4C221588CDF00803884 /* CoreDataRemote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoreDataRemote.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 8B49D4C521588CDF00803884 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 8B49D4C721588CDF00803884 /* FilmListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilmListViewController.swift; sourceTree = ""; }; 26 | 8B49D4CA21588CDF00803884 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 8B49D4CC21588CE000803884 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 8B49D4CF21588CE000803884 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 8B49D4D121588CE000803884 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 8B49D4D7215892EC00803884 /* CoreDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; 31 | 8B49D4D92158A23300803884 /* ApiRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiRepository.swift; sourceTree = ""; }; 32 | 8B49D4DC2158A40B00803884 /* StarWars.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StarWars.xcdatamodel; sourceTree = ""; }; 33 | 8B49D4DE2158A57D00803884 /* Film.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Film.swift; sourceTree = ""; }; 34 | 8B49D4E02158B20D00803884 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 8B49D4BF21588CDF00803884 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 8B49D4B921588CDF00803884 = { 49 | isa = PBXGroup; 50 | children = ( 51 | 8B49D4C421588CDF00803884 /* CoreDataRemote */, 52 | 8B49D4C321588CDF00803884 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 8B49D4C321588CDF00803884 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 8B49D4C221588CDF00803884 /* CoreDataRemote.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 8B49D4C421588CDF00803884 /* CoreDataRemote */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 8B72E3AE215BEBD90008CBBA /* Controllers */, 68 | 8B72E3AC215BEBA80008CBBA /* Data */, 69 | 8B72E3AD215BEBC10008CBBA /* Repository */, 70 | 8B72E3AF215BEC010008CBBA /* Resources */, 71 | ); 72 | path = CoreDataRemote; 73 | sourceTree = ""; 74 | }; 75 | 8B72E3AC215BEBA80008CBBA /* Data */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 8B49D4D7215892EC00803884 /* CoreDataStack.swift */, 79 | 8B49D4DB2158A40B00803884 /* StarWars.xcdatamodeld */, 80 | 8B49D4E02158B20D00803884 /* DataProvider.swift */, 81 | 8B49D4DE2158A57D00803884 /* Film.swift */, 82 | ); 83 | path = Data; 84 | sourceTree = ""; 85 | }; 86 | 8B72E3AD215BEBC10008CBBA /* Repository */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 8B49D4D92158A23300803884 /* ApiRepository.swift */, 90 | ); 91 | path = Repository; 92 | sourceTree = ""; 93 | }; 94 | 8B72E3AE215BEBD90008CBBA /* Controllers */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 8B49D4C721588CDF00803884 /* FilmListViewController.swift */, 98 | ); 99 | path = Controllers; 100 | sourceTree = ""; 101 | }; 102 | 8B72E3AF215BEC010008CBBA /* Resources */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 8B49D4C521588CDF00803884 /* AppDelegate.swift */, 106 | 8B49D4C921588CDF00803884 /* Main.storyboard */, 107 | 8B49D4CC21588CE000803884 /* Assets.xcassets */, 108 | 8B49D4CE21588CE000803884 /* LaunchScreen.storyboard */, 109 | 8B49D4D121588CE000803884 /* Info.plist */, 110 | ); 111 | path = Resources; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXNativeTarget section */ 117 | 8B49D4C121588CDF00803884 /* CoreDataRemote */ = { 118 | isa = PBXNativeTarget; 119 | buildConfigurationList = 8B49D4D421588CE000803884 /* Build configuration list for PBXNativeTarget "CoreDataRemote" */; 120 | buildPhases = ( 121 | 8B49D4BE21588CDF00803884 /* Sources */, 122 | 8B49D4BF21588CDF00803884 /* Frameworks */, 123 | 8B49D4C021588CDF00803884 /* Resources */, 124 | ); 125 | buildRules = ( 126 | ); 127 | dependencies = ( 128 | ); 129 | name = CoreDataRemote; 130 | productName = CoreDataRemote; 131 | productReference = 8B49D4C221588CDF00803884 /* CoreDataRemote.app */; 132 | productType = "com.apple.product-type.application"; 133 | }; 134 | /* End PBXNativeTarget section */ 135 | 136 | /* Begin PBXProject section */ 137 | 8B49D4BA21588CDF00803884 /* Project object */ = { 138 | isa = PBXProject; 139 | attributes = { 140 | LastSwiftUpdateCheck = 1000; 141 | LastUpgradeCheck = 1000; 142 | ORGANIZATIONNAME = "Alfian Losari"; 143 | TargetAttributes = { 144 | 8B49D4C121588CDF00803884 = { 145 | CreatedOnToolsVersion = 10.0; 146 | }; 147 | }; 148 | }; 149 | buildConfigurationList = 8B49D4BD21588CDF00803884 /* Build configuration list for PBXProject "CoreDataRemote" */; 150 | compatibilityVersion = "Xcode 9.3"; 151 | developmentRegion = en; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | Base, 156 | ); 157 | mainGroup = 8B49D4B921588CDF00803884; 158 | productRefGroup = 8B49D4C321588CDF00803884 /* Products */; 159 | projectDirPath = ""; 160 | projectRoot = ""; 161 | targets = ( 162 | 8B49D4C121588CDF00803884 /* CoreDataRemote */, 163 | ); 164 | }; 165 | /* End PBXProject section */ 166 | 167 | /* Begin PBXResourcesBuildPhase section */ 168 | 8B49D4C021588CDF00803884 /* Resources */ = { 169 | isa = PBXResourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 8B49D4D021588CE000803884 /* LaunchScreen.storyboard in Resources */, 173 | 8B49D4CD21588CE000803884 /* Assets.xcassets in Resources */, 174 | 8B49D4CB21588CDF00803884 /* Main.storyboard in Resources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXResourcesBuildPhase section */ 179 | 180 | /* Begin PBXSourcesBuildPhase section */ 181 | 8B49D4BE21588CDF00803884 /* Sources */ = { 182 | isa = PBXSourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | 8B49D4D8215892EC00803884 /* CoreDataStack.swift in Sources */, 186 | 8B49D4DF2158A57D00803884 /* Film.swift in Sources */, 187 | 8B49D4E12158B20D00803884 /* DataProvider.swift in Sources */, 188 | 8B49D4C821588CDF00803884 /* FilmListViewController.swift in Sources */, 189 | 8B49D4DD2158A40B00803884 /* StarWars.xcdatamodeld in Sources */, 190 | 8B49D4DA2158A23300803884 /* ApiRepository.swift in Sources */, 191 | 8B49D4C621588CDF00803884 /* AppDelegate.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin PBXVariantGroup section */ 198 | 8B49D4C921588CDF00803884 /* Main.storyboard */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | 8B49D4CA21588CDF00803884 /* Base */, 202 | ); 203 | name = Main.storyboard; 204 | sourceTree = ""; 205 | }; 206 | 8B49D4CE21588CE000803884 /* LaunchScreen.storyboard */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | 8B49D4CF21588CE000803884 /* Base */, 210 | ); 211 | name = LaunchScreen.storyboard; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXVariantGroup section */ 215 | 216 | /* Begin XCBuildConfiguration section */ 217 | 8B49D4D221588CE000803884 /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | CLANG_ANALYZER_NONNULL = YES; 222 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 224 | CLANG_CXX_LIBRARY = "libc++"; 225 | CLANG_ENABLE_MODULES = YES; 226 | CLANG_ENABLE_OBJC_ARC = YES; 227 | CLANG_ENABLE_OBJC_WEAK = YES; 228 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 229 | CLANG_WARN_BOOL_CONVERSION = YES; 230 | CLANG_WARN_COMMA = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 233 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 234 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 235 | CLANG_WARN_EMPTY_BODY = YES; 236 | CLANG_WARN_ENUM_CONVERSION = YES; 237 | CLANG_WARN_INFINITE_RECURSION = YES; 238 | CLANG_WARN_INT_CONVERSION = YES; 239 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 240 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 241 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 243 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 244 | CLANG_WARN_STRICT_PROTOTYPES = YES; 245 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 246 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | CODE_SIGN_IDENTITY = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu11; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 269 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 270 | MTL_FAST_MATH = YES; 271 | ONLY_ACTIVE_ARCH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 274 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 275 | }; 276 | name = Debug; 277 | }; 278 | 8B49D4D321588CE000803884 /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_ENABLE_OBJC_WEAK = YES; 289 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_COMMA = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | CODE_SIGN_IDENTITY = "iPhone Developer"; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 313 | ENABLE_NS_ASSERTIONS = NO; 314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 324 | MTL_ENABLE_DEBUG_INFO = NO; 325 | MTL_FAST_MATH = YES; 326 | SDKROOT = iphoneos; 327 | SWIFT_COMPILATION_MODE = wholemodule; 328 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 329 | VALIDATE_PRODUCT = YES; 330 | }; 331 | name = Release; 332 | }; 333 | 8B49D4D521588CE000803884 /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 337 | CODE_SIGN_STYLE = Automatic; 338 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 339 | INFOPLIST_FILE = "$(SRCROOT)/CoreDataRemote/Resources/Info.plist"; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | ); 344 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.CoreDataRemote; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 4.2; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | }; 349 | name = Debug; 350 | }; 351 | 8B49D4D621588CE000803884 /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | CODE_SIGN_STYLE = Automatic; 356 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 357 | INFOPLIST_FILE = "$(SRCROOT)/CoreDataRemote/Resources/Info.plist"; 358 | LD_RUNPATH_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "@executable_path/Frameworks", 361 | ); 362 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.CoreDataRemote; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_VERSION = 4.2; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | 8B49D4BD21588CDF00803884 /* Build configuration list for PBXProject "CoreDataRemote" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | 8B49D4D221588CE000803884 /* Debug */, 376 | 8B49D4D321588CE000803884 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | 8B49D4D421588CE000803884 /* Build configuration list for PBXNativeTarget "CoreDataRemote" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | 8B49D4D521588CE000803884 /* Debug */, 385 | 8B49D4D621588CE000803884 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | 392 | /* Begin XCVersionGroup section */ 393 | 8B49D4DB2158A40B00803884 /* StarWars.xcdatamodeld */ = { 394 | isa = XCVersionGroup; 395 | children = ( 396 | 8B49D4DC2158A40B00803884 /* StarWars.xcdatamodel */, 397 | ); 398 | currentVersion = 8B49D4DC2158A40B00803884 /* StarWars.xcdatamodel */; 399 | path = StarWars.xcdatamodeld; 400 | sourceTree = ""; 401 | versionGroupType = wrapper.xcdatamodel; 402 | }; 403 | /* End XCVersionGroup section */ 404 | }; 405 | rootObject = 8B49D4BA21588CDF00803884 /* Project object */; 406 | } 407 | --------------------------------------------------------------------------------