Merge pull request #71 from omnivore-app/feature/iphone-nav-bar
iPhone NavBar Visibility
This commit is contained in:
@ -10,6 +10,7 @@ import WebKit
|
||||
let rawAuthCookie: String?
|
||||
let openLinkAction: (URL) -> Void
|
||||
let webViewActionHandler: (WKScriptMessage) -> Void
|
||||
let navBarVisibilityRatioUpdater: (Double) -> Void
|
||||
@Binding var annotation: String
|
||||
@Binding var annotationSaveTransactionID: UUID?
|
||||
@Binding var sendIncreaseFontSignal: Bool
|
||||
@ -28,11 +29,12 @@ import WebKit
|
||||
let webView = WebView(frame: CGRect.zero)
|
||||
let contentController = WKUserContentController()
|
||||
|
||||
webView.scrollView.isScrollEnabled = true
|
||||
webView.scrollView.contentInset.top = LinkItemDetailView.navBarHeight
|
||||
webView.navigationDelegate = context.coordinator
|
||||
webView.isOpaque = false
|
||||
webView.backgroundColor = UIColor.clear
|
||||
webView.configuration.userContentController = contentController
|
||||
webView.scrollView.delegate = context.coordinator
|
||||
|
||||
for action in WebViewAction.allCases {
|
||||
webView.configuration.userContentController.add(context.coordinator, name: action.rawValue)
|
||||
@ -53,6 +55,7 @@ import WebKit
|
||||
|
||||
context.coordinator.linkHandler = openLinkAction
|
||||
context.coordinator.webViewActionHandler = webViewActionHandler
|
||||
context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater
|
||||
|
||||
return webView
|
||||
}
|
||||
|
||||
@ -1,17 +1,28 @@
|
||||
// import Models
|
||||
import SwiftUI
|
||||
// import Utils
|
||||
import WebKit
|
||||
|
||||
final class WebAppViewCoordinator: NSObject {
|
||||
let navBarHeight = LinkItemDetailView.navBarHeight
|
||||
var webViewActionHandler: (WKScriptMessage) -> Void = { _ in }
|
||||
var linkHandler: (URL) -> Void = { _ in }
|
||||
var needsReload = true
|
||||
var lastSavedAnnotationID: UUID?
|
||||
var updateNavBarVisibilityRatio: (Double) -> Void = { _ in }
|
||||
private var yOffsetAtStartOfDrag: Double?
|
||||
private var lastYOffset: Double = 0
|
||||
private var hasDragged = false
|
||||
private var isNavBarHidden = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
var navBarVisibilityRatio: Double = 1.0 {
|
||||
didSet {
|
||||
isNavBarHidden = navBarVisibilityRatio == 0
|
||||
updateNavBarVisibilityRatio(navBarVisibilityRatio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WebAppViewCoordinator: WKScriptMessageHandler {
|
||||
@ -34,6 +45,60 @@ extension WebAppViewCoordinator: WKNavigationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension WebAppViewCoordinator: UIScrollViewDelegate {
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
hasDragged = true
|
||||
yOffsetAtStartOfDrag = scrollView.contentOffset.y + scrollView.contentInset.top
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard hasDragged else { return }
|
||||
|
||||
let yOffset = scrollView.contentOffset.y
|
||||
|
||||
if yOffset == 0 {
|
||||
scrollView.contentInset.top = navBarHeight
|
||||
navBarVisibilityRatio = 1
|
||||
return
|
||||
}
|
||||
|
||||
if yOffset < 0 {
|
||||
navBarVisibilityRatio = 1
|
||||
scrollView.contentInset.top = navBarHeight
|
||||
return
|
||||
}
|
||||
|
||||
if yOffset < navBarHeight {
|
||||
let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset
|
||||
navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / navBarHeight))
|
||||
scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight
|
||||
return
|
||||
}
|
||||
|
||||
guard let yOffsetAtStartOfDrag = yOffsetAtStartOfDrag else { return }
|
||||
|
||||
if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden {
|
||||
let translation = yOffset - yOffsetAtStartOfDrag
|
||||
let ratio = translation < navBarHeight ? 1 - (translation / navBarHeight) : 0
|
||||
navBarVisibilityRatio = min(ratio, 1)
|
||||
scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) {
|
||||
scrollView.contentInset.top = navBarHeight
|
||||
navBarVisibilityRatio = 1
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
||||
scrollView.contentInset.top = navBarHeight
|
||||
navBarVisibilityRatio = 1
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
struct WebViewConfig {
|
||||
let url: URL
|
||||
let themeId: String
|
||||
|
||||
@ -35,9 +35,11 @@ public struct WebAppWrapperView: View {
|
||||
@State private var annotation = String()
|
||||
@State var annotationSaveTransactionID: UUID?
|
||||
@State var safariWebLink: SafariWebLink?
|
||||
let navBarVisibilityRatioUpdater: (Double) -> Void
|
||||
|
||||
public init(viewModel: WebAppWrapperViewModel) {
|
||||
public init(viewModel: WebAppWrapperViewModel, navBarVisibilityRatioUpdater: ((Double) -> Void)? = nil) {
|
||||
self.viewModel = viewModel
|
||||
self.navBarVisibilityRatioUpdater = navBarVisibilityRatioUpdater ?? { _ in }
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
@ -53,6 +55,7 @@ public struct WebAppWrapperView: View {
|
||||
#endif
|
||||
},
|
||||
webViewActionHandler: webViewActionHandler,
|
||||
navBarVisibilityRatioUpdater: navBarVisibilityRatioUpdater,
|
||||
annotation: $annotation,
|
||||
annotationSaveTransactionID: $annotationSaveTransactionID,
|
||||
sendIncreaseFontSignal: $viewModel.sendIncreaseFontSignal,
|
||||
|
||||
@ -23,6 +23,7 @@ public struct FontSizeAdjustmentPopoverView: View {
|
||||
Image(systemName: "minus")
|
||||
#if os(iOS)
|
||||
.foregroundColor(.appGraySolid)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
)
|
||||
@ -37,6 +38,7 @@ public struct FontSizeAdjustmentPopoverView: View {
|
||||
Image(systemName: "plus")
|
||||
#if os(iOS)
|
||||
.foregroundColor(.appGraySolid)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
)
|
||||
|
||||
@ -25,8 +25,12 @@ public final class LinkItemDetailViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
public struct LinkItemDetailView: View {
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
|
||||
static let navBarHeight = 50.0
|
||||
@ObservedObject private var viewModel: LinkItemDetailViewModel
|
||||
@State private var showFontSizePopover = false
|
||||
@State private var navBarVisibilityRatio = 1.0
|
||||
|
||||
public init(viewModel: LinkItemDetailViewModel) {
|
||||
self.viewModel = viewModel
|
||||
@ -50,13 +54,105 @@ public struct LinkItemDetailView: View {
|
||||
)
|
||||
}
|
||||
|
||||
var fontAdjustmentPopoverView: some View {
|
||||
FontSizeAdjustmentPopoverView(
|
||||
increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true },
|
||||
decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true }
|
||||
)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
innerBody
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
if UIDevice.isIPhone, !viewModel.item.isPDF {
|
||||
compactInnerBody
|
||||
} else {
|
||||
innerBody
|
||||
}
|
||||
#else
|
||||
innerBody
|
||||
#endif
|
||||
}
|
||||
|
||||
var navBar: some View {
|
||||
HStack(alignment: .center) {
|
||||
Button(
|
||||
action: { self.presentationMode.wrappedValue.dismiss() },
|
||||
label: {
|
||||
Image(systemName: "chevron.backward")
|
||||
.font(.appTitleTwo)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
)
|
||||
.scaleEffect(navBarVisibilityRatio)
|
||||
Spacer()
|
||||
Button(
|
||||
action: { showFontSizePopover.toggle() },
|
||||
label: {
|
||||
Image(systemName: "textformat.size")
|
||||
.font(.appTitleTwo)
|
||||
}
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.scaleEffect(navBarVisibilityRatio)
|
||||
}
|
||||
.frame(height: LinkItemDetailView.navBarHeight * navBarVisibilityRatio)
|
||||
.opacity(navBarVisibilityRatio)
|
||||
.background(Color.systemBackground)
|
||||
}
|
||||
|
||||
@ViewBuilder private var compactInnerBody: some View {
|
||||
if let webAppWrapperViewModel = viewModel.webAppWrapperViewModel {
|
||||
ZStack {
|
||||
WebAppWrapperView(
|
||||
viewModel: webAppWrapperViewModel,
|
||||
navBarVisibilityRatioUpdater: {
|
||||
if $0 < 1 {
|
||||
showFontSizePopover = false
|
||||
}
|
||||
navBarVisibilityRatio = $0
|
||||
}
|
||||
)
|
||||
if showFontSizePopover {
|
||||
VStack {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.frame(height: LinkItemDetailView.navBarHeight)
|
||||
HStack {
|
||||
Spacer()
|
||||
fontAdjustmentPopoverView
|
||||
.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.background(
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
showFontSizePopover = false
|
||||
}
|
||||
)
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
navBar
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
navBar
|
||||
Spacer()
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.performActionSubject.send(.load)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var innerBody: some View {
|
||||
if let pdfURL = viewModel.item.pdfURL {
|
||||
#if os(iOS)
|
||||
@ -76,17 +172,11 @@ public struct LinkItemDetailView: View {
|
||||
)
|
||||
#if os(iOS)
|
||||
.fittedPopover(isPresented: $showFontSizePopover) {
|
||||
FontSizeAdjustmentPopoverView(
|
||||
increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true },
|
||||
decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true }
|
||||
)
|
||||
fontAdjustmentPopoverView
|
||||
}
|
||||
#else
|
||||
.popover(isPresented: $showFontSizePopover) {
|
||||
FontSizeAdjustmentPopoverView(
|
||||
increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true },
|
||||
decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true }
|
||||
)
|
||||
fontAdjustmentPopoverView
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -103,3 +193,15 @@ public struct LinkItemDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable swipe to go back behavior if nav bar is hidden
|
||||
extension UINavigationController: UIGestureRecognizerDelegate {
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
interactivePopGestureRecognizer?.delegate = self
|
||||
}
|
||||
|
||||
public func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool {
|
||||
viewControllers.count > 1
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user