diff --git a/trySwift.xcodeproj/project.pbxproj b/trySwift.xcodeproj/project.pbxproj index 16ced1b..e02d0d6 100644 --- a/trySwift.xcodeproj/project.pbxproj +++ b/trySwift.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 499CCFF21CC2E0F4007A5BBB /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499CCFF11CC2E0F4007A5BBB /* UIViewControllerExtension.swift */; }; 49F7B2811E8475F900F09768 /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F7B2801E8475F900F09768 /* SplitViewController.swift */; }; 4D498DB1B98D4B496FDBB7AA /* Pods_try__Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD2E9D5733A6E1B6E10AFEEF /* Pods_try__Extension.framework */; }; + 5C7F82DB2048B01D009193B4 /* RootTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7F82DA2048B01D009193B4 /* RootTabBarController.swift */; }; 5CDB20792048952A00C3E0D3 /* TwitterFollowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDB20782048952A00C3E0D3 /* TwitterFollowDelegate.swift */; }; 7AD1E19C80B78BB08E3DF079 /* Pods_trySwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 620220ABCCCDF47FDAF48B67 /* Pods_trySwift.framework */; }; EAFE1C26E49EFABF83487BDC /* Pods_try__Today.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24718BF11477753FCD47A7A8 /* Pods_try__Today.framework */; }; @@ -194,6 +195,7 @@ 499CCFF11CC2E0F4007A5BBB /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; }; 49F7B2801E8475F900F09768 /* SplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = ""; }; 58F81AD508535BD405F98215 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 5C7F82DA2048B01D009193B4 /* RootTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootTabBarController.swift; sourceTree = ""; }; 5CDB20782048952A00C3E0D3 /* TwitterFollowDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterFollowDelegate.swift; sourceTree = ""; }; 5FA07FBC036240AEF8B7C739 /* Pods-trySwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-trySwift.release.xcconfig"; path = "Pods/Target Support Files/Pods-trySwift/Pods-trySwift.release.xcconfig"; sourceTree = ""; }; 620220ABCCCDF47FDAF48B67 /* Pods_trySwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_trySwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -383,6 +385,14 @@ name = Frameworks; sourceTree = ""; }; + 5C7F82D92048B00B009193B4 /* RootTabBar */ = { + isa = PBXGroup; + children = ( + 5C7F82DA2048B01D009193B4 /* RootTabBarController.swift */, + ); + path = RootTabBar; + sourceTree = ""; + }; 5CDB20772048951F00C3E0D3 /* Twitter */ = { isa = PBXGroup; children = ( @@ -425,6 +435,7 @@ FA39E8C51C6AB7310074B6BE /* ViewControllers */ = { isa = PBXGroup; children = ( + 5C7F82D92048B00B009193B4 /* RootTabBar */, FA39E8FB1C6C26150074B6BE /* Schedule */, FA39E9061C6C43480074B6BE /* Speakers */, FABA73BF1D6D9ACE0081D887 /* OfficeHours */, @@ -1138,6 +1149,7 @@ files = ( 5CDB20792048952A00C3E0D3 /* TwitterFollowDelegate.swift in Sources */, 499BD62A1D05910200E74061 /* Twitter.swift in Sources */, + 5C7F82DB2048B01D009193B4 /* RootTabBarController.swift in Sources */, FA39E90F1C6C81870074B6BE /* SponsorsViewController.swift in Sources */, FA36B7071E5DA3970022E6A9 /* DateFormatterExtension.swift in Sources */, FAA54F141D9130F900EC9E80 /* UITableViewExtension.swift in Sources */, diff --git a/trySwift/Base.lproj/Main.storyboard b/trySwift/Base.lproj/Main.storyboard index 57700a5..f0dc6c0 100644 --- a/trySwift/Base.lproj/Main.storyboard +++ b/trySwift/Base.lproj/Main.storyboard @@ -9,10 +9,10 @@ - + - + diff --git a/trySwift/RootTabBar/RootTabBarController.swift b/trySwift/RootTabBar/RootTabBarController.swift new file mode 100644 index 0000000..250b6bf --- /dev/null +++ b/trySwift/RootTabBar/RootTabBarController.swift @@ -0,0 +1,61 @@ +// +// RootTabBarController.swift +// trySwift +// +// Created by Sash Zats on 3/2/18. +// Copyright © 2018 NatashaTheRobot. All rights reserved. +// + +import UIKit + +class RootTabBarController: UITabBarController, UITabBarControllerDelegate { + override func viewDidLoad() { + super.viewDidLoad() + self.delegate = self + } + + func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { + guard + let splitViewController = viewController as? UISplitViewController, + let navigationController = splitViewController.viewControllers.last as? UINavigationController + else { + return + } + + // if several view controllers are in the stack, pop to the root + if navigationController.viewControllers.count > 1 { + navigationController.popToRootViewController(animated: true) + } else { + // if there's at least one view controller in the stack (which there always should be) + if let firstController = navigationController.viewControllers.first { + // we either delegate to the controller since it knows better how to scroll to the top + if let scrollableToTop = firstController as? ScrollableToTop { + scrollableToTop.scrollAfterTabTap() + // or we find the topmost scroll view and scroll it to the top + } else { + firstController.view.findScrollSubview()?.setContentOffset(.zero, animated: true) + } + } + } + } +} + +protocol ScrollableToTop { + func scrollAfterTabTap() +} + +private extension UIView { + func findScrollSubview() -> UIScrollView? { + if let scrollView = self as? UIScrollView, + scrollView.contentSize.width < scrollView.contentSize.height { + return scrollView + } + for subview in self.subviews { + if let scrollView = subview.findScrollSubview(), + scrollView.contentSize.width < scrollView.contentSize.height { + return scrollView + } + } + return nil + } +} diff --git a/trySwift/ScheduleViewController.swift b/trySwift/ScheduleViewController.swift index a4e6751..28bb910 100644 --- a/trySwift/ScheduleViewController.swift +++ b/trySwift/ScheduleViewController.swift @@ -30,8 +30,6 @@ class ScheduleViewController: ButtonBarPagerTabStripViewController { buttonBarView.backgroundColor = .white settings.style.selectedBarBackgroundColor = .white buttonBarView.selectedBar.backgroundColor = .trySwiftAccentColor() - - tabBarController?.delegate = self } override func viewDidAppear(_ animated: Bool) { @@ -47,7 +45,6 @@ class ScheduleViewController: ButtonBarPagerTabStripViewController { override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { return days.map { SessionsTableViewController(conferenceDay: $0, scheduleViewController: self) } - } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @@ -59,6 +56,13 @@ class ScheduleViewController: ButtonBarPagerTabStripViewController { } } +extension ScheduleViewController: ScrollableToTop { + func scrollAfterTabTap() { + let controller = viewControllers[currentIndex] as! SessionsTableViewController + controller.scrollAfterTabTap() + } +} + private extension ScheduleViewController { @discardableResult @@ -77,19 +81,3 @@ private extension ScheduleViewController { } } -extension ScheduleViewController: UITabBarControllerDelegate { - func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { - guard - tabBarController.selectedViewController === viewController, - navigationController?.viewControllers.last === self - else { return true } - - guard - let index = moveToCorrectDate(animated: true), - let controller = viewControllers[index] as? SessionsTableViewController - else { return true } - controller.scrollToCurrentSession(animated: true) - - return true - } -} diff --git a/trySwift/SessionsTableViewController.swift b/trySwift/SessionsTableViewController.swift index 03acb17..2fe07d5 100644 --- a/trySwift/SessionsTableViewController.swift +++ b/trySwift/SessionsTableViewController.swift @@ -10,6 +10,7 @@ import UIKit import XLPagerTabStrip import TrySwiftData + class SessionsTableViewController: UITableViewController { private lazy var needsToScrollToCurrentSession = Calendar.current.isDateInToday(conferenceDay.date) @@ -33,15 +34,15 @@ class SessionsTableViewController: UITableViewController { super.viewDidLoad() configureTableView() - + if traitCollection.forceTouchCapability == .available { registerForPreviewing(with: self, sourceView: tableView) } } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + if needsToScrollToCurrentSession { needsToScrollToCurrentSession = false scrollToCurrentSession(animated: false) @@ -55,7 +56,7 @@ class SessionsTableViewController: UITableViewController { let isCollapsed = splitViewController?.isCollapsed, !isCollapsed, !didShowDetail else { return } - + didShowDetail = true scheduleViewController?.performSegue(withIdentifier: sessionDetailsSegue, sender: firstSelectableSessionVC) } @@ -63,24 +64,24 @@ class SessionsTableViewController: UITableViewController { // MARK: - Table view data source extension SessionsTableViewController { - + override func numberOfSections(in tableView: UITableView) -> Int { return conferenceDay.sessionBlocks.count } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return conferenceDay.sessionBlocks[section].sessions.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SessionTableViewCell - + let session = conferenceDay.sessionBlocks[indexPath.section].sessions[indexPath.row] cell.configure(withSession: session) - + return cell } - + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { let session = conferenceDay.sessionBlocks[section] let sessionDateFormatter = DateFormatter.sessionDateFormatter @@ -108,7 +109,7 @@ extension SessionsTableViewController: IndicatorInfoProvider { } extension SessionsTableViewController: UIViewControllerPreviewingDelegate { - + func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { guard let indexPath = tableView.indexPathForRow(at: location) else { return nil } // This will show the cell clearly and blur the rest of the screen for our peek. @@ -116,25 +117,25 @@ extension SessionsTableViewController: UIViewControllerPreviewingDelegate { let session = conferenceDay.sessionBlocks[indexPath.section].sessions[indexPath.row] return viewController(for: session) } - + func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { scheduleViewController?.performSegue(withIdentifier: sessionDetailsSegue, sender: viewControllerToCommit) } } extension SessionsTableViewController { - + func configureTableView() { - + tableView.register(SessionTableViewCell.self) - + tableView.estimatedRowHeight = 160 tableView.rowHeight = UITableViewAutomaticDimension } } private extension SessionsTableViewController { - + func viewController(for session: Session) -> UIViewController? { switch session.type { case .talk, .lightningTalk: @@ -174,10 +175,10 @@ private extension SessionsTableViewController { default: return nil } - + return nil } - + func sessionDetails(_ presentation: Presentation, session: Session) -> UIViewController { let storyboard = UIStoryboard(name: "Main", bundle: nil) let sessionDetailsVC = storyboard.instantiateViewController(withIdentifier: String(describing: SessionDetailsViewController.self)) as! SessionDetailsViewController @@ -185,28 +186,28 @@ private extension SessionsTableViewController { sessionDetailsVC.presentation = presentation return sessionDetailsVC } - + func officeHourDetails(_ speaker: Speaker, session: Session) -> UIViewController { let officeHoursVC = OfficeHoursDetailViewController() officeHoursVC.speaker = speaker officeHoursVC.session = session return officeHoursVC } - + func webDisplay(_ event: Event) -> UIViewController { let webViewController = WebDisplayViewController() webViewController.url = URL(string: event.website!) webViewController.displayTitle = event.title return webViewController } - + func webDisplay(_ sponsor: Sponsor) -> UIViewController { let webViewController = WebDisplayViewController() webViewController.url = URL(string: sponsor.url!) webViewController.displayTitle = sponsor.name return webViewController } - + func venueDetails(_ venue: Venue) -> UIViewController { let venueDetailsVC = VenueTableViewController(venue: venue) venueDetailsVC.tableView.contentInset = UIEdgeInsets(top: 80,left: 0,bottom: 0,right: 0) @@ -224,7 +225,7 @@ private extension SessionsTableViewController { } extension SessionsTableViewController { - + func scrollToCurrentSession(animated: Bool) { let secondsFromGMT = TimeZone.current.secondsFromGMT() guard @@ -232,7 +233,13 @@ extension SessionsTableViewController { let section = conferenceDay.sessionBlocks.index(where: { date < $0.endTime }), !conferenceDay.sessionBlocks[section].sessions.isEmpty else { return } - + tableView.scrollToRow(at: IndexPath(row: 0, section: section), at: .top, animated: animated) } } + +extension SessionsTableViewController: ScrollableToTop { + func scrollAfterTabTap() { + scrollToCurrentSession(animated: true) + } +}