diff --git a/WireGuard/ViewControllers/UITableView+WireGuard.swift b/WireGuard/ViewControllers/UITableView+WireGuard.swift new file mode 100644 index 0000000..5bb38a9 --- /dev/null +++ b/WireGuard/ViewControllers/UITableView+WireGuard.swift @@ -0,0 +1,36 @@ +// +// UITableView+WireGuard.swift +// WireGuard +// +// Created by Jeroen Leenarts on 24-05-18. +// Copyright © 2018 WireGuard. All rights reserved. +// + +import Foundation + +import UIKit + +extension UITableView { + + func register(type: T.Type, prefix: String = "") where T: UITableViewCell { + register(type, forCellReuseIdentifier: prefix + type.identifier) + } + + func dequeueReusableCell(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UITableViewCell { + return dequeueReusableCell(withIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast + } + + func registerNib(type: T.Type, prefix: String = "") where T: UITableViewCell { + let nib = UINib(nibName: prefix + type.identifier, bundle: nil) + register(nib, forCellReuseIdentifier: prefix + type.identifier) + } + + func registerNib(type: T.Type, prefix: String = "") where T: UITableViewHeaderFooterView { + let nib = UINib(nibName: prefix + type.identifier, bundle: nil) + register(nib, forHeaderFooterViewReuseIdentifier: prefix + type.identifier) + } + + func dequeueReusableHeaderFooterView(type: T.Type, prefix: String = "") -> T where T: UITableViewHeaderFooterView { + return dequeueReusableHeaderFooterView(withIdentifier: prefix + type.identifier) as! T // swiftlint:disable:this force_cast + } +} diff --git a/Wireguard.xcodeproj/project.pbxproj b/Wireguard.xcodeproj/project.pbxproj index 87ff566..90ec6e3 100644 --- a/Wireguard.xcodeproj/project.pbxproj +++ b/Wireguard.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 4A4BAD0E20B5F6C300F12B28 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD0D20B5F6C300F12B28 /* Coordinator.swift */; }; 4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD0F20B5F6EC00F12B28 /* RootCoordinator.swift */; }; 4A4BAD1320B5F82400F12B28 /* Identifyable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1220B5F82400F12B28 /* Identifyable.swift */; }; - 4A4BAD1720B5F8DE00F12B28 /* Wireguard.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1520B5F8DE00F12B28 /* Wireguard.xcdatamodeld */; }; + 4A4BAD1720B5F8DE00F12B28 /* WireGuard.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */; }; 4A4BAD1A20B5F8FF00F12B28 /* Profile+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1820B5F8FF00F12B28 /* Profile+CoreDataClass.swift */; }; 4A4BAD1B20B5F8FF00F12B28 /* Profile+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1920B5F8FF00F12B28 /* Profile+CoreDataProperties.swift */; }; 4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1C20B6026900F12B28 /* Peer+CoreDataProperties.swift */; }; @@ -28,6 +28,7 @@ 4A4BAD2320B6026900F12B28 /* Interface+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4BAD1F20B6026900F12B28 /* Interface+CoreDataClass.swift */; }; 4A7F6EDD20B674CD00B260B7 /* Address+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7F6EDB20B674CD00B260B7 /* Address+CoreDataClass.swift */; }; 4A7F6EDE20B674CD00B260B7 /* Address+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7F6EDC20B674CD00B260B7 /* Address+CoreDataProperties.swift */; }; + 4A8AABD820B6A79100B6D8C1 /* UITableView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -58,7 +59,7 @@ 4A4BAD0D20B5F6C300F12B28 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 4A4BAD0F20B5F6EC00F12B28 /* RootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootCoordinator.swift; sourceTree = ""; }; 4A4BAD1220B5F82400F12B28 /* Identifyable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identifyable.swift; sourceTree = ""; }; - 4A4BAD1620B5F8DE00F12B28 /* Wireguard.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Wireguard.xcdatamodel; sourceTree = ""; }; + 4A4BAD1620B5F8DE00F12B28 /* WireGuard.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WireGuard.xcdatamodel; sourceTree = ""; }; 4A4BAD1820B5F8FF00F12B28 /* Profile+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+CoreDataClass.swift"; sourceTree = ""; }; 4A4BAD1920B5F8FF00F12B28 /* Profile+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+CoreDataProperties.swift"; sourceTree = ""; }; 4A4BAD1C20B6026900F12B28 /* Peer+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Peer+CoreDataProperties.swift"; path = "/Users/jeroenleenarts/code/wireguard-ios/Wireguard/Models/Peer+CoreDataProperties.swift"; sourceTree = ""; }; @@ -67,6 +68,7 @@ 4A4BAD1F20B6026900F12B28 /* Interface+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Interface+CoreDataClass.swift"; path = "/Users/jeroenleenarts/code/wireguard-ios/Wireguard/Models/Interface+CoreDataClass.swift"; sourceTree = ""; }; 4A7F6EDB20B674CD00B260B7 /* Address+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataClass.swift"; sourceTree = ""; }; 4A7F6EDC20B674CD00B260B7 /* Address+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataProperties.swift"; sourceTree = ""; }; + 4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+WireGuard.swift"; sourceTree = ""; }; 82069F3AE97A82448F990CFB /* Pods-Wireguard.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wireguard.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Wireguard/Pods-Wireguard.debug.xcconfig"; sourceTree = ""; }; 861983CAE8FDC13BC83E7E04 /* Pods_WireGuard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WireGuard.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8BF50C7EC60CD91BBA05E51F /* Pods_Wireguard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Wireguard.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -160,6 +162,7 @@ children = ( 4A4BAD1220B5F82400F12B28 /* Identifyable.swift */, 4A4BACE720B5F1BF00F12B28 /* ConnectionsTableViewController.swift */, + 4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */, ); path = ViewControllers; sourceTree = ""; @@ -175,7 +178,7 @@ 4A4BAD1E20B6026900F12B28 /* Interface+CoreDataProperties.swift */, 4A4BAD1820B5F8FF00F12B28 /* Profile+CoreDataClass.swift */, 4A4BAD1920B5F8FF00F12B28 /* Profile+CoreDataProperties.swift */, - 4A4BAD1520B5F8DE00F12B28 /* Wireguard.xcdatamodeld */, + 4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */, ); path = Models; sourceTree = ""; @@ -393,7 +396,7 @@ 4A4BAD2220B6026900F12B28 /* Interface+CoreDataProperties.swift in Sources */, 4A7F6EDE20B674CD00B260B7 /* Address+CoreDataProperties.swift in Sources */, 4A4BAD1320B5F82400F12B28 /* Identifyable.swift in Sources */, - 4A4BAD1720B5F8DE00F12B28 /* Wireguard.xcdatamodeld in Sources */, + 4A4BAD1720B5F8DE00F12B28 /* WireGuard.xcdatamodeld in Sources */, 4A4BAD1A20B5F8FF00F12B28 /* Profile+CoreDataClass.swift in Sources */, 4A4BACE820B5F1BF00F12B28 /* ConnectionsTableViewController.swift in Sources */, 4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */, @@ -401,6 +404,7 @@ 4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */, 4A4BAD2320B6026900F12B28 /* Interface+CoreDataClass.swift in Sources */, 4A7F6EDD20B674CD00B260B7 /* Address+CoreDataClass.swift in Sources */, + 4A8AABD820B6A79100B6D8C1 /* UITableView+WireGuard.swift in Sources */, 4A4BAD2120B6026900F12B28 /* Peer+CoreDataClass.swift in Sources */, 4A4BAD1B20B5F8FF00F12B28 /* Profile+CoreDataProperties.swift in Sources */, 4A4BACE620B5F1BF00F12B28 /* AppDelegate.swift in Sources */, @@ -674,13 +678,13 @@ /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ - 4A4BAD1520B5F8DE00F12B28 /* Wireguard.xcdatamodeld */ = { + 4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - 4A4BAD1620B5F8DE00F12B28 /* Wireguard.xcdatamodel */, + 4A4BAD1620B5F8DE00F12B28 /* WireGuard.xcdatamodel */, ); - currentVersion = 4A4BAD1620B5F8DE00F12B28 /* Wireguard.xcdatamodel */; - path = Wireguard.xcdatamodeld; + currentVersion = 4A4BAD1620B5F8DE00F12B28 /* WireGuard.xcdatamodel */; + path = WireGuard.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; diff --git a/Wireguard/Base.lproj/Main.storyboard b/Wireguard/Base.lproj/Main.storyboard index 5703677..f1dff1e 100644 --- a/Wireguard/Base.lproj/Main.storyboard +++ b/Wireguard/Base.lproj/Main.storyboard @@ -9,21 +9,30 @@ - + - + - + - - + + + + + @@ -32,6 +41,14 @@ + + + + + + + + diff --git a/Wireguard/Coordinators/AppCoordinator.swift b/Wireguard/Coordinators/AppCoordinator.swift index 442a54f..b03807f 100644 --- a/Wireguard/Coordinators/AppCoordinator.swift +++ b/Wireguard/Coordinators/AppCoordinator.swift @@ -86,17 +86,21 @@ class AppCoordinator: RootViewCoordinator { extension AppCoordinator: ConnectionsTableViewControllerDelegate { func addProvider(connectionsTableViewController: ConnectionsTableViewController) { // TODO implement + print("Add provider") } - func settings(connectionsTableViewController: ConnectionsTableViewController) { + func connect(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) { // TODO implement + print("connect profile \(profile)") } - func connect(profile: Profile) { + func configure(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) { // TODO implement + print("configure profile \(profile)") } - func delete(profile: Profile) { + func delete(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) { // TODO implement + print("delete profile \(profile)") } } diff --git a/Wireguard/Models/Wireguard.xcdatamodeld/Wireguard.xcdatamodel/contents b/Wireguard/Models/WireGuard.xcdatamodeld/WireGuard.xcdatamodel/contents similarity index 100% rename from Wireguard/Models/Wireguard.xcdatamodeld/Wireguard.xcdatamodel/contents rename to Wireguard/Models/WireGuard.xcdatamodeld/WireGuard.xcdatamodel/contents diff --git a/Wireguard/ViewControllers/ConnectionsTableViewController.swift b/Wireguard/ViewControllers/ConnectionsTableViewController.swift index bfd7942..931945d 100644 --- a/Wireguard/ViewControllers/ConnectionsTableViewController.swift +++ b/Wireguard/ViewControllers/ConnectionsTableViewController.swift @@ -13,9 +13,9 @@ import BNRCoreDataStack protocol ConnectionsTableViewControllerDelegate: class { func addProvider(connectionsTableViewController: ConnectionsTableViewController) - func settings(connectionsTableViewController: ConnectionsTableViewController) - func connect(profile: Profile) - func delete(profile: Profile) + func connect(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) + func configure(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) + func delete(profile: Profile, connectionsTableViewController: ConnectionsTableViewController) } class ConnectionsTableViewController: UITableViewController { @@ -23,16 +23,154 @@ class ConnectionsTableViewController: UITableViewController { var viewContext: NSManagedObjectContext! + private lazy var fetchedResultsController: FetchedResultsController = { + let fetchRequest = NSFetchRequest() + fetchRequest.entity = Profile.entity() + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)] + let frc = FetchedResultsController(fetchRequest: fetchRequest, + managedObjectContext: viewContext) + frc.setDelegate(self.frcDelegate) + return frc + }() + + private lazy var frcDelegate: ProfileFetchedResultsControllerDelegate = { // swiftlint:disable:this weak_delegate + return ProfileFetchedResultsControllerDelegate(tableView: self.tableView) + }() + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + do { + try fetchedResultsController.performFetch() + } catch { + print("Failed to fetch objects: \(error)") + } } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. + @IBAction func addProvider(_ sender: Any) { + delegate?.addProvider(connectionsTableViewController: self) } + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fetchedResultsController.sections?[0].objects.count ?? 0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(type: ProfileTableViewCell.self, for: indexPath) + + guard let sections = fetchedResultsController.sections else { + fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil") + } + + let section = sections[indexPath.section] + let profile = section.objects[indexPath.row] + + cell.textLabel?.text = profile.title + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let sections = fetchedResultsController.sections else { + fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil") + } + + let section = sections[indexPath.section] + let profile = section.objects[indexPath.row] + + delegate?.connect(profile: profile, connectionsTableViewController: self) + + tableView.deselectRow(at: indexPath, animated: true) + } + + override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { + guard let sections = fetchedResultsController.sections else { + fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil") + } + + let section = sections[indexPath.section] + let profile = section.objects[indexPath.row] + + delegate?.configure(profile: profile, connectionsTableViewController: self) + + } + + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } + + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + + guard let sections = fetchedResultsController.sections else { + fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil") + } + + let section = sections[indexPath.section] + let profile = section.objects[indexPath.row] + + delegate?.delete(profile: profile, connectionsTableViewController: self) + } + } } extension ConnectionsTableViewController: Identifyable {} + +class ProfileFetchedResultsControllerDelegate: NSObject, FetchedResultsControllerDelegate { + + private weak var tableView: UITableView? + + // MARK: - Lifecycle + init(tableView: UITableView) { + self.tableView = tableView + } + + func fetchedResultsControllerDidPerformFetch(_ controller: FetchedResultsController) { + tableView?.reloadData() + } + + func fetchedResultsControllerWillChangeContent(_ controller: FetchedResultsController) { + tableView?.beginUpdates() + } + + func fetchedResultsControllerDidChangeContent(_ controller: FetchedResultsController) { + tableView?.endUpdates() + } + + func fetchedResultsController(_ controller: FetchedResultsController, didChangeObject change: FetchedResultsObjectChange) { + guard let tableView = tableView else { return } + switch change { + case let .insert(_, indexPath): + tableView.insertRows(at: [indexPath], with: .automatic) + + case let .delete(_, indexPath): + tableView.deleteRows(at: [indexPath], with: .automatic) + + case let .move(_, fromIndexPath, toIndexPath): + tableView.moveRow(at: fromIndexPath, to: toIndexPath) + + case let .update(_, indexPath): + tableView.reloadRows(at: [indexPath], with: .automatic) + } + } + + func fetchedResultsController(_ controller: FetchedResultsController, didChangeSection change: FetchedResultsSectionChange) { + guard let tableView = tableView else { return } + switch change { + case let .insert(_, index): + tableView.insertSections(IndexSet(integer: index), with: .automatic) + + case let .delete(_, index): + tableView.deleteSections(IndexSet(integer: index), with: .automatic) + } + } +} + +class ProfileTableViewCell: UITableViewCell { + +} + +extension ProfileTableViewCell: Identifyable {}