-
-
Notifications
You must be signed in to change notification settings - Fork 831
Talk Page Archives - Part 1 #4441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
251e2fc
Restore menu item, add basic container and content views, pass in obs…
tonisevener e6da7ff
Add ShiftingTopViewsStack in helper setup method
tonisevener 33b5ab9
Add ShiftingTopView superclass
tonisevener fb6a8a4
Add ShiftingNavigationBarView
tonisevener b3f2fda
Insert bar view into archives
tonisevener 6224167
Add ShiftingScrollView, set up data communication
tonisevener 023db14
Add demo header view
tonisevener b9fd58c
Fix double nav bar issue in lower OSes
tonisevener bd71afc
Merge branch 'main' into talk-page-archives-1
mazevedofs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import UIKit | ||
|
|
||
| class DemoShiftingThreeLineHeaderView: ShiftingTopView, Themeable { | ||
|
|
||
| private(set) var theme: Theme | ||
|
|
||
| private lazy var headerView: ThreeLineHeaderView = { | ||
| let view = ThreeLineHeaderView() | ||
| view.topSmallLine.text = "Test 1" | ||
| view.middleLargeLine.text = "Test 2" | ||
| view.bottomSmallLine.text = "Test 3" | ||
| view.translatesAutoresizingMaskIntoConstraints = false | ||
| return view | ||
| }() | ||
|
|
||
| private var topConstraint: NSLayoutConstraint? | ||
|
|
||
| init(shiftOrder: Int, theme: Theme) { | ||
| self.theme = theme | ||
| super.init(shiftOrder: shiftOrder) | ||
| } | ||
|
|
||
| required init?(coder aDecoder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| override func setup() { | ||
| super.setup() | ||
|
|
||
| addSubview(headerView) | ||
|
|
||
| let top = headerView.topAnchor.constraint(equalTo: topAnchor) | ||
| let bottom = bottomAnchor.constraint(equalTo: headerView.bottomAnchor) | ||
| let leading = headerView.leadingAnchor.constraint(equalTo: leadingAnchor) | ||
| let trailing = trailingAnchor.constraint(equalTo: headerView.trailingAnchor) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| top, | ||
| bottom, | ||
| leading, | ||
| trailing | ||
| ]) | ||
|
|
||
| self.topConstraint = top | ||
| clipsToBounds = true | ||
| apply(theme: theme) | ||
| } | ||
|
|
||
| // MARK: Overrides | ||
|
|
||
| override var contentHeight: CGFloat { | ||
| return headerView.frame.height | ||
| } | ||
|
|
||
| private var isFullyHidden: Bool { | ||
| return -(topConstraint?.constant ?? 0) == contentHeight | ||
| } | ||
|
|
||
| override func shift(amount: CGFloat) -> ShiftingTopView.AmountShifted { | ||
|
|
||
| let limitedShiftAmount = max(0, min(amount, contentHeight)) | ||
|
|
||
| let percent = limitedShiftAmount / contentHeight | ||
| alpha = 1.0 - percent | ||
|
|
||
| if (self.topConstraint?.constant ?? 0) != -limitedShiftAmount { | ||
| self.topConstraint?.constant = -limitedShiftAmount | ||
| } | ||
|
|
||
| return limitedShiftAmount | ||
| } | ||
|
|
||
| // MARK: Themeable | ||
|
|
||
| func apply(theme: Theme) { | ||
| self.theme = theme | ||
| backgroundColor = theme.colors.paperBackground | ||
| headerView.apply(theme: theme) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import UIKit | ||
|
|
||
| class ShiftingNavigationBarView: ShiftingTopView, Themeable { | ||
|
|
||
| private let navigationItems: [UINavigationItem] | ||
| private weak var popDelegate: UIViewController? // Navigation back actions will be forwarded to this view controller | ||
|
|
||
| private var topConstraint: NSLayoutConstraint? | ||
|
|
||
| private lazy var bar: UINavigationBar = { | ||
| let bar = UINavigationBar() | ||
| bar.translatesAutoresizingMaskIntoConstraints = false | ||
| bar.delegate = self | ||
| return bar | ||
| }() | ||
|
|
||
| init(shiftOrder: Int, navigationItems: [UINavigationItem], popDelegate: UIViewController) { | ||
| self.navigationItems = navigationItems | ||
| self.popDelegate = popDelegate | ||
| super.init(shiftOrder: shiftOrder) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| override func setup() { | ||
| super.setup() | ||
|
|
||
| bar.setItems(navigationItems, animated: false) | ||
| addSubview(bar) | ||
|
|
||
| let top = bar.topAnchor.constraint(equalTo: topAnchor) | ||
| let bottom = bottomAnchor.constraint(equalTo: bar.bottomAnchor) | ||
| let leading = bar.leadingAnchor.constraint(equalTo: leadingAnchor) | ||
| let trailing = trailingAnchor.constraint(equalTo: bar.trailingAnchor) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| top, | ||
| bottom, | ||
| leading, | ||
| trailing | ||
| ]) | ||
|
|
||
| self.topConstraint = top | ||
|
|
||
| clipsToBounds = true | ||
| } | ||
|
|
||
| // MARK: Overrides | ||
|
|
||
| override var contentHeight: CGFloat { | ||
| return bar.frame.height | ||
| } | ||
|
|
||
| private var isFullyHidden: Bool { | ||
| return -(topConstraint?.constant ?? 0) == contentHeight | ||
| } | ||
|
|
||
| override func shift(amount: CGFloat) -> ShiftingTopView.AmountShifted { | ||
|
|
||
| // Only allow navigation bar to move just out of frame | ||
| let limitedShiftAmount = max(0, min(amount, contentHeight)) | ||
|
|
||
| // Shrink and fade | ||
| let percent = limitedShiftAmount / contentHeight | ||
| let barScaleTransform = CGAffineTransformMakeScale(1.0 - (percent/2), 1.0 - (percent/2)) | ||
| for subview in self.bar.subviews { | ||
| for subview in subview.subviews { | ||
| subview.transform = barScaleTransform | ||
| } | ||
| } | ||
| alpha = 1.0 - percent | ||
|
|
||
| // Shift Y placement | ||
| if (self.topConstraint?.constant ?? 0) != -limitedShiftAmount { | ||
| self.topConstraint?.constant = -limitedShiftAmount | ||
| } | ||
|
|
||
| return limitedShiftAmount | ||
| } | ||
|
|
||
| // MARK: Themeable | ||
|
|
||
| func apply(theme: Theme) { | ||
| bar.setBackgroundImage(theme.navigationBarBackgroundImage, for: .default) | ||
| bar.titleTextAttributes = theme.navigationBarTitleTextAttributes | ||
| bar.isTranslucent = false | ||
| bar.barTintColor = theme.colors.chromeBackground | ||
| bar.shadowImage = theme.navigationBarShadowImage | ||
| bar.tintColor = theme.colors.chromeText | ||
| } | ||
| } | ||
|
|
||
| // MARK: Themeable | ||
|
|
||
| extension ShiftingNavigationBarView: UINavigationBarDelegate { | ||
| func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { | ||
| popDelegate?.navigationController?.popViewController(animated: true) | ||
| return false | ||
| } | ||
|
|
||
| // Taken from NavigationBar.swift | ||
| func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) { | ||
| // During iOS 14's long press to access back history, this function is called *after* the unneeded navigationItems have been popped off. | ||
| // However, with our custom navBar the actual articleVC isn't changed. So we need to find the articleVC for the top navItem, and pop to it. | ||
| // This should be in `shouldPop`, but as of iOS 14.0, `shouldPop` isn't called when long pressing a back button. Once this is fixed by Apple, | ||
| // we should move this to `shouldPop` to improve animations. (Update: A bug tracker was filed w/ Apple, and this won't be fixed anytime soon. | ||
| // Apple: "This is expected behavior. Due to side effects that many clients have in the shouldPop handler, we do not consult it when using the back | ||
| // button menu. We instead recommend that you hide the back button when you wish to disallow popping past a particular point in the navigation stack.") | ||
| if let topNavigationItem = navigationBar.items?.last, | ||
| let navController = popDelegate?.navigationController, | ||
| let tappedViewController = navController.viewControllers.first(where: {$0.navigationItem == topNavigationItem}) { | ||
| popDelegate?.navigationController?.popToViewController(tappedViewController, animated: true) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import SwiftUI | ||
|
|
||
| struct ShiftingScrollView<Content: View>: View { | ||
|
|
||
| @EnvironmentObject var data: ShiftingTopViewsData | ||
|
|
||
| let axes: Axis.Set | ||
| let showsIndicators: Bool | ||
| let content: Content | ||
|
|
||
| init( | ||
| axes: Axis.Set = .vertical, | ||
| showsIndicators: Bool = true, | ||
| @ViewBuilder content: () -> Content | ||
| ) { | ||
| self.axes = axes | ||
| self.showsIndicators = showsIndicators | ||
| self.content = content() | ||
| } | ||
|
|
||
| var body: some View { | ||
| ScrollView(axes, showsIndicators: showsIndicators) { | ||
| ZStack(alignment:.topLeading) { | ||
| GeometryReader { geometry in | ||
| Color.clear.preference( | ||
| key: ScrollOffsetPreferenceKey.self, | ||
| value: geometry.frame(in: .named("scrollView")).origin | ||
| ) | ||
| } | ||
| .frame(width: 0, height: 0) | ||
| content | ||
| .padding(.top, data.totalHeight) | ||
| } | ||
| } | ||
| .coordinateSpace(name: "scrollView") | ||
| .onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: offsetChanged) | ||
| } | ||
|
|
||
| func offsetChanged(_ offset: CGPoint) { | ||
| data.scrollAmount = -offset.y | ||
| } | ||
| } | ||
|
|
||
| private struct ScrollOffsetPreferenceKey: PreferenceKey { | ||
| static var defaultValue: CGPoint = .zero | ||
|
|
||
| static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import Foundation | ||
| import CocoaLumberjackSwift | ||
|
|
||
| class ShiftingTopView: SetupView { | ||
| typealias AmountShifted = CGFloat | ||
|
|
||
| weak var stackView: ShiftingTopViewsStack? | ||
| let shiftOrder: Int | ||
|
|
||
| init(shiftOrder: Int) { | ||
| self.shiftOrder = shiftOrder | ||
| super.init(frame: .zero) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| override func setup() { | ||
| super.setup() | ||
|
|
||
| translatesAutoresizingMaskIntoConstraints = false | ||
| } | ||
|
|
||
| override func layoutSubviews() { | ||
| super.layoutSubviews() | ||
|
|
||
| if stackView == nil { | ||
| DDLogError("Missing stackView assignment in ShiftingSubview, which could potentially cause incorrect content inset/padding calculations.") | ||
| } | ||
| stackView?.calculateTotalHeight() | ||
| } | ||
|
|
||
| var contentHeight: CGFloat { | ||
| assertionFailure("Must override") | ||
| return 0 | ||
| } | ||
|
|
||
| func shift(amount: CGFloat) -> AmountShifted { | ||
| assertionFailure("Must override") | ||
| return 0 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import Foundation | ||
| import SwiftUI | ||
|
|
||
| protocol ShiftingTopViewsContaining: UIViewController { | ||
| var shiftingTopViewsStack: ShiftingTopViewsStack? { get set } | ||
| } | ||
|
|
||
| // Note: This subclass only seems necessary for proper nav bar hiding in iOS 14 & 15. It can be removed and switched to raw UIHostingControllers for iOS16+ | ||
| private class NavigationBarHidingHostingVC<Content: View>: UIHostingController<Content> { | ||
|
|
||
| override func viewWillAppear(_ animated: Bool) { | ||
| super.viewWillAppear(animated) | ||
|
|
||
| self.navigationController?.isNavigationBarHidden = true | ||
| } | ||
| } | ||
|
|
||
| extension ShiftingTopViewsContaining { | ||
| func setup(shiftingTopViews: [ShiftingTopView], swiftuiView: some View, observableTheme: ObservableTheme) { | ||
|
|
||
| navigationController?.isNavigationBarHidden = true | ||
|
|
||
| let shiftingTopViewsStack = ShiftingTopViewsStack() | ||
|
|
||
| // Add needed environment objects to SwiftUI view, then embed hosting view controller, then embed SwiftUI hosting view controller | ||
| let finalSwiftUIView = swiftuiView | ||
| .environmentObject(shiftingTopViewsStack.data) | ||
| .environmentObject(observableTheme) | ||
|
|
||
| let childHostingVC: UIViewController | ||
|
|
||
| if #available(iOS 16, *) { | ||
| childHostingVC = UIHostingController(rootView: finalSwiftUIView) | ||
| } else { | ||
| childHostingVC = NavigationBarHidingHostingVC(rootView: finalSwiftUIView) | ||
| } | ||
|
|
||
| childHostingVC.view.translatesAutoresizingMaskIntoConstraints = false | ||
| view.addSubview(childHostingVC.view) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: childHostingVC.view.topAnchor), | ||
| view.safeAreaLayoutGuide.leadingAnchor.constraint(equalTo: childHostingVC.view.leadingAnchor), | ||
| view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: childHostingVC.view.trailingAnchor), | ||
| view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: childHostingVC.view.bottomAnchor) | ||
| ]) | ||
|
|
||
| addChild(childHostingVC) | ||
| childHostingVC.didMove(toParent: self) | ||
| childHostingVC.view.backgroundColor = .clear | ||
|
|
||
| // Add shiftingTopViewsStack | ||
| view.addSubview(shiftingTopViewsStack) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: shiftingTopViewsStack.topAnchor), | ||
| view.leadingAnchor.constraint(equalTo: shiftingTopViewsStack.leadingAnchor), | ||
| view.trailingAnchor.constraint(equalTo: shiftingTopViewsStack.trailingAnchor) | ||
| ]) | ||
|
|
||
| shiftingTopViewsStack.addShiftingTopViews(shiftingTopViews) | ||
| shiftingTopViewsStack.apply(theme: observableTheme.theme) | ||
| self.shiftingTopViewsStack = shiftingTopViewsStack | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import Foundation | ||
| import Combine | ||
|
|
||
| class ShiftingTopViewsData: ObservableObject { | ||
| @Published var scrollAmount = CGFloat(0) | ||
| @Published var totalHeight = CGFloat(0) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to prevent the bar from hiding when VoiceOver is on or provide another way for VoiceOver users to go back from this view when it is hiding (This can be done on the part 2 PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch, I hadn't thought about Voice Over yet. 😅 I'll spin it off into a Part 3 PR since Part 2 was getting pretty big.