Move more navigation responsibilities into the AppCoordinator

This commit is contained in:
Maurice Parker 2019-07-05 17:45:39 -05:00
parent 19f8f8bc97
commit 15754684a4
6 changed files with 62 additions and 63 deletions

View File

@ -23,7 +23,12 @@ public extension Notification.Name {
class AppCoordinator { class AppCoordinator {
static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5) private var rootSplitViewController: UISplitViewController!
private var masterNavigationController: UINavigationController!
private var masterFeedViewController: MasterFeedViewController!
private var masterTimelineViewController: MasterTimelineViewController?
private let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
private var articleRowMap = [String: Int]() // articleID: rowIndex private var articleRowMap = [String: Int]() // articleID: rowIndex
@ -185,6 +190,21 @@ class AppCoordinator {
} }
func start() -> UIViewController {
rootSplitViewController = (UIStoryboard.main.instantiateInitialViewController() as! UISplitViewController)
rootSplitViewController.delegate = self
masterNavigationController = (rootSplitViewController.viewControllers.first as! UINavigationController)
masterFeedViewController = UIStoryboard.main.instantiateController(ofType: MasterFeedViewController.self)
masterFeedViewController.coordinator = self
masterNavigationController.pushViewController(masterFeedViewController, animated: false)
// let detailNavigationController = (rootSplitViewController.viewControllers.last as! UINavigationController)
// detailNavigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
return rootSplitViewController
}
// MARK: Notifications // MARK: Notifications
@objc func containerChildrenDidChange(_ note: Notification) { @objc func containerChildrenDidChange(_ note: Notification) {
@ -226,6 +246,24 @@ class AppCoordinator {
// MARK: API // MARK: API
func didSelectFeed(_ indexPath: IndexPath) {
masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self)
masterTimelineViewController!.coordinator = self
currentMasterIndexPath = indexPath
masterNavigationController.pushViewController(masterTimelineViewController!, animated: true)
}
func didSelectArticle(_ indexPath: IndexPath) {
let detailViewController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self)
detailViewController.coordinator = self
detailViewController.navigationItem.leftBarButtonItem = rootSplitViewController.displayModeButtonItem
detailViewController.navigationItem.leftItemsSupplementBackButton = true
currentArticleIndexPath = indexPath
// rootSplitViewController.toggleMasterView()
rootSplitViewController.showDetailViewController(detailViewController, sender: self)
}
func beginUpdates() { func beginUpdates() {
animatingChanges = true animatingChanges = true
} }
@ -608,7 +646,7 @@ private extension AppCoordinator {
} }
func queueFetchAndMergeArticles() { func queueFetchAndMergeArticles() {
AppCoordinator.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles)) fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles))
} }
@objc func fetchAndMergeArticles() { @objc func fetchAndMergeArticles() {

View File

@ -19,9 +19,6 @@
<rect key="frame" x="0.0" y="813" width="414" height="49"/> <rect key="frame" x="0.0" y="813" width="414" height="49"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</toolbar> </toolbar>
<connections>
<segue destination="7bK-jq-Zjz" kind="relationship" relationship="rootViewController" id="tsl-Nk-0bq"/>
</connections>
</navigationController> </navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="8fS-aE-onr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="8fS-aE-onr" sceneMemberID="firstResponder"/>
</objects> </objects>
@ -36,7 +33,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<wkWebView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="t8d-md-Yhc"> <wkWebView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="t8d-md-Yhc">
<rect key="frame" x="0.0" y="88" width="414" height="725"/> <rect key="frame" x="0.0" y="44" width="414" height="769"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<wkWebViewConfiguration key="configuration"> <wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/> <audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
@ -155,9 +152,6 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="208"/> <rect key="frame" x="0.0" y="0.0" width="414" height="208"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView> </tableViewCellContentView>
<connections>
<segue destination="vC3-pB-5Vb" kind="showDetail" identifier="showDetail" id="RT3-gH-cyN"/>
</connections>
</tableViewCell> </tableViewCell>
</prototypes> </prototypes>
<connections> <connections>
@ -206,7 +200,7 @@
<!--Feeds--> <!--Feeds-->
<scene sceneID="smW-Zh-WAh"> <scene sceneID="smW-Zh-WAh">
<objects> <objects>
<tableViewController storyboardIdentifier="MasterViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="7bK-jq-Zjz" customClass="MasterFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController storyboardIdentifier="MasterFeedViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="7bK-jq-Zjz" customClass="MasterFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="r7i-6Z-zg0"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="r7i-6Z-zg0">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -263,12 +257,8 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Skn-vK-czG"> <toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Skn-vK-czG">
<rect key="frame" x="0.0" y="813" width="414" height="49"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</toolbar> </toolbar>
<connections>
<segue destination="JEX-9P-axG" kind="relationship" relationship="rootViewController" id="GKi-kA-LjT"/>
</connections>
</navigationController> </navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="SLD-UC-DBI" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="SLD-UC-DBI" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
@ -284,7 +274,4 @@
<image name="square.and.arrow.up" catalog="system" width="56" height="64"/> <image name="square.and.arrow.up" catalog="system" width="56" height="64"/>
<image name="star" catalog="system" width="64" height="58"/> <image name="star" catalog="system" width="64" height="58"/>
</resources> </resources>
<inferredMetricsTieBreakers>
<segue reference="RT3-gH-cyN"/>
</inferredMetricsTieBreakers>
</document> </document>

View File

@ -23,7 +23,7 @@ class DetailViewController: UIViewController {
@IBOutlet weak var browserBarButtonItem: UIBarButtonItem! @IBOutlet weak var browserBarButtonItem: UIBarButtonItem!
@IBOutlet weak var webView: WKWebView! @IBOutlet weak var webView: WKWebView!
weak var coordinator: AppCoordinator? weak var coordinator: AppCoordinator!
override func viewDidLoad() { override func viewDidLoad() {
@ -47,14 +47,14 @@ class DetailViewController: UIViewController {
} }
func markAsRead() { func markAsRead() {
if let article = coordinator?.currentArticle { if let article = coordinator.currentArticle {
markArticles(Set([article]), statusKey: .read, flag: true) markArticles(Set([article]), statusKey: .read, flag: true)
} }
} }
func updateUI() { func updateUI() {
guard let article = coordinator?.currentArticle else { guard let article = coordinator.currentArticle else {
nextUnreadBarButtonItem.isEnabled = false nextUnreadBarButtonItem.isEnabled = false
prevArticleBarButtonItem.isEnabled = false prevArticleBarButtonItem.isEnabled = false
nextArticleBarButtonItem.isEnabled = false nextArticleBarButtonItem.isEnabled = false
@ -65,9 +65,9 @@ class DetailViewController: UIViewController {
return return
} }
nextUnreadBarButtonItem.isEnabled = coordinator?.isAnyUnreadAvailable ?? false nextUnreadBarButtonItem.isEnabled = coordinator.isAnyUnreadAvailable
prevArticleBarButtonItem.isEnabled = coordinator?.isPrevArticleAvailable ?? false prevArticleBarButtonItem.isEnabled = coordinator.isPrevArticleAvailable
nextArticleBarButtonItem.isEnabled = coordinator?.isNextArticleAvailable ?? false nextArticleBarButtonItem.isEnabled = coordinator.isNextArticleAvailable
readBarButtonItem.isEnabled = true readBarButtonItem.isEnabled = true
starBarButtonItem.isEnabled = true starBarButtonItem.isEnabled = true
@ -80,7 +80,7 @@ class DetailViewController: UIViewController {
let starImage = article.status.starred ? AppAssets.starClosedImage : AppAssets.starOpenImage let starImage = article.status.starred ? AppAssets.starClosedImage : AppAssets.starOpenImage
starBarButtonItem.image = starImage starBarButtonItem.image = starImage
if let timelineName = coordinator?.timelineName { if let timelineName = coordinator.timelineName {
if navigationController?.navigationItem.backBarButtonItem?.title != timelineName { if navigationController?.navigationItem.backBarButtonItem?.title != timelineName {
let backItem = UIBarButtonItem(title: timelineName, style: .plain, target: nil, action: nil) let backItem = UIBarButtonItem(title: timelineName, style: .plain, target: nil, action: nil)
navigationController?.navigationItem.backBarButtonItem = backItem navigationController?.navigationItem.backBarButtonItem = backItem
@ -91,7 +91,7 @@ class DetailViewController: UIViewController {
func reloadHTML() { func reloadHTML() {
guard let article = coordinator?.currentArticle, let webView = webView else { guard let article = coordinator.currentArticle, let webView = webView else {
return return
} }
let style = ArticleStylesManager.shared.currentStyle let style = ArticleStylesManager.shared.currentStyle
@ -110,7 +110,7 @@ class DetailViewController: UIViewController {
guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else { guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else {
return return
} }
if articles.count == 1 && articles.first?.articleID == coordinator?.currentArticle?.articleID { if articles.count == 1 && articles.first?.articleID == coordinator.currentArticle?.articleID {
updateUI() updateUI()
} }
} }
@ -132,41 +132,41 @@ class DetailViewController: UIViewController {
// MARK: Actions // MARK: Actions
@IBAction func nextUnread(_ sender: Any) { @IBAction func nextUnread(_ sender: Any) {
coordinator?.selectNextUnread() coordinator.selectNextUnread()
} }
@IBAction func prevArticle(_ sender: Any) { @IBAction func prevArticle(_ sender: Any) {
coordinator?.currentArticleIndexPath = coordinator?.prevArticleIndexPath coordinator.currentArticleIndexPath = coordinator.prevArticleIndexPath
} }
@IBAction func nextArticle(_ sender: Any) { @IBAction func nextArticle(_ sender: Any) {
coordinator?.currentArticleIndexPath = coordinator?.nextArticleIndexPath coordinator.currentArticleIndexPath = coordinator.nextArticleIndexPath
} }
@IBAction func toggleRead(_ sender: Any) { @IBAction func toggleRead(_ sender: Any) {
if let article = coordinator?.currentArticle { if let article = coordinator.currentArticle {
markArticles(Set([article]), statusKey: .read, flag: !article.status.read) markArticles(Set([article]), statusKey: .read, flag: !article.status.read)
} }
} }
@IBAction func toggleStar(_ sender: Any) { @IBAction func toggleStar(_ sender: Any) {
if let article = coordinator?.currentArticle { if let article = coordinator.currentArticle {
markArticles(Set([article]), statusKey: .starred, flag: !article.status.starred) markArticles(Set([article]), statusKey: .starred, flag: !article.status.starred)
} }
} }
@IBAction func openBrowser(_ sender: Any) { @IBAction func openBrowser(_ sender: Any) {
guard let preferredLink = coordinator?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { guard let preferredLink = coordinator.currentArticle?.preferredLink, let url = URL(string: preferredLink) else {
return return
} }
UIApplication.shared.open(url, options: [:]) UIApplication.shared.open(url, options: [:])
} }
@IBAction func showActivityDialog(_ sender: Any) { @IBAction func showActivityDialog(_ sender: Any) {
guard let preferredLink = coordinator?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { guard let preferredLink = coordinator.currentArticle?.preferredLink, let url = URL(string: preferredLink) else {
return return
} }
let itemSource = ArticleActivityItemSource(url: url, subject: coordinator?.currentArticle?.title) let itemSource = ArticleActivityItemSource(url: url, subject: coordinator.currentArticle?.title)
let activityViewController = UIActivityViewController(activityItems: [itemSource], applicationActivities: nil) let activityViewController = UIActivityViewController(activityItems: [itemSource], applicationActivities: nil)
activityViewController.popoverPresentationController?.barButtonItem = self.actionBarButtonItem activityViewController.popoverPresentationController?.barButtonItem = self.actionBarButtonItem

View File

@ -276,12 +276,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
coordinator.didSelectFeed(indexPath)
let timeline = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self)
timeline.coordinator = coordinator
coordinator.currentMasterIndexPath = indexPath
self.navigationController?.pushViewController(timeline, animated: true)
} }
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {

View File

@ -63,16 +63,6 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand
resignFirstResponder() resignFirstResponder()
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.coordinator = coordinator
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
splitViewController?.toggleMasterView()
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection) super.traitCollectionDidChange(previousTraitCollection)
@ -185,7 +175,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
coordinator.currentArticleIndexPath = indexPath coordinator.didSelectArticle(indexPath)
} }
// MARK: Notifications // MARK: Notifications

View File

@ -19,18 +19,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window!.tintColor = AppAssets.netNewsWireBlueColor window!.tintColor = AppAssets.netNewsWireBlueColor
window!.rootViewController = coordinator.start()
let splitViewController = UIStoryboard.main.instantiateInitialViewController() as! UISplitViewController
splitViewController.delegate = coordinator
window!.rootViewController = splitViewController
let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
let masterFeedViewController = masterNavigationController.topViewController as! MasterFeedViewController
masterFeedViewController.coordinator = coordinator
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
// if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { // if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
// if !configure(window: window, with: userActivity) { // if !configure(window: window, with: userActivity) {