Reputation: 41
I'm working on a fractal clock app that displays animated fractals based on clock hands in its main view. I want users of my app to be able to enter a fullscreen mode where all unnecessary UI is temporarily hidden and only the animation remains visible. The behavior I'm looking for is similar to Apple's Photos app where one can tap on the currently displayed image so that the navigation bar, the bottom bar, the status bar and the home indicator fade out until the image is tapped again.
Hiding the navigation bar and the status bar was as easy as finding the right view modifiers to pass the hiding condition to. But as far as I know it is currently not possible in SwiftUI to hide the home indicator without bringing in UIKit.
On Stack Overflow I found this solution by Casper Zandbergen for conditionally hiding the home indicator and adopted it for my project. It works but sadly in comes with an unacceptable side effect: The main view now no longer extends under the status bar and the home indicator which has two implications:
I hope somebody with decent UIKit experience can help me with this. Please keep in mind that I'm a beginner in SwiftUI and that I have basically no prior experience with UIKit. Thanks in advance!
import SwiftUI
struct ContentView: View {
@StateObject var settings = Settings()
@State private var showSettings = false
@State private var hideUI = false
var body: some View {
NavigationView {
GeometryReader { proxy in
let radius = 0.5 * min(proxy.size.width, proxy.size.height) - 20
FractalClockView(settings: settings, clockRadius: radius)
}
.ignoresSafeArea(.all)
.toolbar {
Button(
action: { showSettings.toggle() },
label: { Label("Settings", systemImage: "slider.horizontal.3") }
)
.popover(isPresented: $showSettings) { SettingsView(settings: settings) }
}
.navigationBarTitleDisplayMode(.inline)
.onTapGesture {
withAnimation { hideUI.toggle() }
}
.navigationBarHidden(hideUI)
.statusBar(hidden: hideUI)
.prefersHomeIndicatorAutoHidden(hideUI) // Code by Amzd
}
.navigationViewStyle(.stack)
}
}
Upvotes: 0
Views: 1881
Reputation: 41
I was able to solve the problem with the SwiftUI view not extending beyond the safe area insets for the status bar and the home indicator by completely switching to a storyboard based project template and embedding my views through a custom UIHostingController as described in this solution by Casper Zandbergen. Before I was re-integrating the hosting controller into the SwiftUI view hierarchy by wrapping it with a UIViewRepresentable instance, which must have caused the complications in handling the safe area.
By managing the whole app through the custom UIHostingController subclass it was even easier to get the hiding of the home indicator working. As much as I love SwiftUI I had to realize that, with its current limitations, UIKit was the better option here.
Final code (optimized version of the solution linked above):
ViewController.swift
import SwiftUI
import UIKit
struct HideUIPreferenceKey: PreferenceKey {
static var defaultValue: Bool = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue() || value
}
}
extension View {
func userInterfaceHidden(_ value: Bool) -> some View {
preference(key: HideUIPreferenceKey.self, value: value)
}
}
class ViewController: UIHostingController<AnyView> {
init() {
weak var vc: ViewController? = nil
super.init(
rootView: AnyView(
ContentView()
.onPreferenceChange(HideUIPreferenceKey.self) {
vc?.userInterfaceHidden = $0
}
)
)
vc = self
}
@objc required dynamic init?(coder: NSCoder) {
weak var vc: ViewController? = nil
super.init(
coder: coder,
rootView: AnyView(
ContentView()
.onPreferenceChange(HideUIPreferenceKey.self) {
vc?.userInterfaceHidden = $0
}
)
)
vc = self
}
private var userInterfaceHidden = false {
didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() }
}
override var prefersStatusBarHidden: Bool {
userInterfaceHidden
}
override var prefersHomeIndicatorAutoHidden: Bool {
userInterfaceHidden
}
}
Upvotes: 0