// // ContentView.swift // Gemini-iOS // // Created by Shadowfacts on 7/15/20. // import SwiftUI import BrowserCore struct ContentView: View { @ObservedObject private var navigator: NavigationManager @State private var urlFieldContents: String private let shareCurrentURL: () -> Void @State private var prevScrollOffset: CGFloat = 0 @State private var scrollOffset: CGFloat = 0 { didSet { prevScrollOffset = oldValue } } @State private var barOffset: CGFloat = 0 @State private var navBarHeight: CGFloat = 0 @State private var toolBarHeight: CGFloat = 0 init(navigator: NavigationManager, shareCurrentURL: @escaping () -> Void) { self.navigator = navigator self._urlFieldContents = State(initialValue: navigator.currentURL.absoluteString) self.shareCurrentURL = shareCurrentURL } var body: some View { ZStack { GeometryReader { (outer: GeometryProxy) in ScrollView(.vertical) { Color.clear.frame(height: navBarHeight) BrowserView(navigator: navigator, scrollingEnabled: false) .background(GeometryReader { (inner: GeometryProxy) in Color.clear.preference(key: ScrollOffsetPrefKey.self, value: -inner.frame(in: .global).minY + outer.frame(in: .global).minY) }) Color.clear.frame(height: toolBarHeight) } .onPreferenceChange(ScrollOffsetPrefKey.self) { scrollOffset = $0 let delta = scrollOffset - prevScrollOffset // When certain state changes happen, the scroll view seems to "scroll" by the top safe area inset. // It's not actually user scrolling, and this screws up our animation, so we ignore it. guard abs(delta) != outer.safeAreaInsets.top else { return } if scrollOffset < 0 { barOffset = 0 } else { if delta != 0 { barOffset += delta } print(barOffset) barOffset = max(0, min(navBarHeight + outer.safeAreaInsets.top, barOffset)) } } } VStack(spacing: 0) { NavigationBar(navigator: navigator) .background(GeometryReader { (geom: GeometryProxy) in Color.clear.preference(key: NavBarHeightPrefKey.self, value: geom.frame(in: .global).height) }) .offset(y: -barOffset) Spacer() ToolBar(navigator: navigator, shareCurrentURL: shareCurrentURL) .background(GeometryReader { (geom: GeometryProxy) in Color.clear.preference(key: ToolBarHeightPrefKey.self, value: geom.frame(in: .global).height) }) .offset(y: barOffset) } .onPreferenceChange(NavBarHeightPrefKey.self) { navBarHeight = $0 print("nav bar height: \($0)") } .onPreferenceChange(ToolBarHeightPrefKey.self) { toolBarHeight = $0 print("tool bar height: \($0)") } } .onAppear(perform: tweakAppearance) .onReceive(navigator.$currentURL, perform: { (new) in urlFieldContents = new.absoluteString }) } private func tweakAppearance() { UIScrollView.appearance().keyboardDismissMode = .interactive } private func commitURL() { guard let url = URL(string: urlFieldContents) else { return } navigator.changeURL(url) } } fileprivate struct ScrollOffsetPrefKey: PreferenceKey { typealias Value = CGFloat static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value += nextValue() } } fileprivate struct NavBarHeightPrefKey: PreferenceKey { typealias Value = CGFloat static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value += nextValue() } } fileprivate struct ToolBarHeightPrefKey: PreferenceKey { typealias Value = CGFloat static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value += nextValue() } } fileprivate enum ScrollDirection { case up, down, none } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!), shareCurrentURL: {}) } }