Hide the Home Indicator in Swift 2.0 - ios

I'm looking to hide the Home Indicator and while this is straightforward in Swift doesn't appear to be as easy in SwiftUI.
I attempted to use this:
How to hide the home indicator with SwiftUI?
But with the removal of the SceneDelegate I'm too green to know how to properly translate that for the new app protocol.
Anyone have any thoughts?
Dan

Here is possible approach to replace default WindowGroup window's hosting controller with any custom one (in this case w/o home indicator).
The helper extension are taken from before provided solution in https://stackoverflow.com/a/63276688/12299030.
Tested with Xcode 12 / iOS 14
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
Text("") // << temporary placeholder
.withHostingWindow { window in
let contentView = ContentView().environmentObject(SomeObservableObject())
window?.rootViewController =
HideHomeIndicatorController(rootView: contentView)
}
}
}
}
and simplified variant of hosting controller to hide home indicator
class HideHomeIndicatorController<Content:View>: UIHostingController<Content> {
override var prefersHomeIndicatorAutoHidden: Bool {
true
}
}

Related

SwiftUI bug with navigation view rotation?

While testing my app to make sure it behaves properly under screen rotations, I discovered that navigation links do not work after a certain sequence of rotations.
The following is a MWE:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("link") {
Text("hi")
}
}
}
// rotate -> back -> link -> rotate -> back -> link
}
On a iPhone 13 Pro Max (iOS 15.2 (19C51)) simulator, the following leads to an error:
Run the app on the portrait mode
Rotate the app to the landscape mode
Touch the back button (in the navigation bar)
Touch the navigation link link
Rotate the app to the portrait mode
Touch the back button
Now touching the navigation link link does not work!
Also, the debug console prints:
Unbalanced calls to begin/end appearance transitions for <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentGS1_VVS_22_VariadicView_Children7ElementVS_24NavigationColumnModifier_GVS_18StyleContextWriterVS_19SidebarStyleContext___: 0x152f24860>.
Is this a bug in SwiftUI?
And is there a way to work around this issue?
I'm on macOS Monterey (12.2 (21D49)) + Xcode 13.2.1 (13C100).
Changing ColumnNavigationViewStyle to StackNavigationViewStyle will solve your problem, the sequence you mentioned is most probably a bug, hopefully apple will solve it soon.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("link") {
Text("hi")
}
}
.navigationViewStyle(.stack) //Style
}
}
Without having to give up double column layout in iPadOS, I used the following conditional view builder to use different navigation style based on the horizontal size class.
(Although conditional view builders often do not play well with animations, it seems ok to use it here.)
extension View {
#ViewBuilder func `if`<TrueContent: View>(
_ condition: Bool,
then trueContent: (Self) -> TrueContent
) -> some View {
if condition {
trueContent(self)
} else {
self
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("link") {
Text("hi")
}
}
.if(UIDevice.current.userInterfaceIdiom == .phone) {
$0.navigationViewStyle(.stack)
}
}
}
P.S. The issue can be reproduced in iPadOS as well when the scene is resized appropriately after each rotation, but I think this is less likely to happen without intention to reproduce.
The max uses columns in landscape and you are missing the second column View. Try this
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("link") {
Text("hi")
}
Text("Select a link")
}
}
}

Reset main content view - Swift UI

My app has one main screen that the user uses, then once they're done go to another view, currently implemented as a .fullscreencover. I want the user to be able to press a button and the app pretty much resets, turning everything back to the way it is when the app is launched for the first time and resetting all variables and classes.
The one method I have tried is opening the view again on top of the final view, however this doesn't reset it. Here is the code I have tried but doesn't work:
Button("New Game"){
newGame.toggle()
}
.fullScreenCover(isPresented: $newGame){
ContentView()
}
Alongside this I have tried navigation views however this causes more issues with the functionality of my app.
Is there a line of code that allows you to do this?
The possible approach is to use global app state
class AppState: ObservableObject {
static let shared = AppState()
#Published var gameID = UUID()
}
and have root content view be dependent on that gameID
#main
struct SomeApp: App {
#StateObject var appState = AppState.shared // << here
var body: some Scene {
WindowGroup {
ContentView().id(appState.gameID) // << here
}
}
}
and now to reset everything to initial state from any place we just set new gameID:
Button("New Game"){
AppState.shared.gameID = UUID()
}

SwiftUI Navigation Bar Title

I'm making my very first steps into SwiftUI following HackingWithSwift course. I have a problem trying to implement navigation bar in my app. For some reason the title does not show up.
Here's the code I'm using:
struct ContentView: View {
var body: some View {
NavigationView {
Form {
...
}
}
.navigationBarTitle(Text("WeSplit"))
}
}
Once running it in simulator or on my target device I see free space for the navigation bar title but no text there.
I've also tried typing .navigationBarTitle("WeSplit") with no Text() in there. Result's still the same.
Do you have any ideas on how to fix it? Thank you in advance!
I'm running Xcode Version 12.3.
It should be inside NavigationView, like
struct ContentView: View {
var body: some View {
NavigationView {
Form {
...
}
.navigationBarTitle(Text("WeSplit")) // << here !!
}
}
}

How to show a SwiftUI View programmatically without a button

I have a problem right now:
What I want: The first time the app starts my already existing view should be presented. I already implemented something in the AppDelegate that checks if the app launched for the first time. And if thats the case another view should be presented. Is there a method to do this directly in the AppDelegate like it was possible with Storyboards?
Thank you in advance.
In your AppDelegate you have a hosting controller that bootstraps the main SwiftUI view. So one way to achieve this is to conditionally set the rootView.
UIHostingController(rootView: isFirstTime ? FirstTimeView() : ContentView())
I would create an initial RootView that merely switches between content and provides an EnvironmentValues that's passed to it.
struct RootView: View {
#Environment(\.isInitialLaunch) var isInitialLaunch: Bool
var body: some View {
Group {
if isInitialLaunch {
FirstTimeView()
} else {
ContentView()
}
}
}
Then, in SceneDelegate:
self.window?.rootViewController = UIHostingController(rootView: RootView().environment(\.isInitialLaunch, isInitialLaunch))
Or, make isInitialLaunch a #State (or #Binding, #ObservedObject, etc.) variable. This way, after your onboarding process, if you change it to false, SwiftUI will actually automatically animate users to the ContentView.

How to re-initialise classes in a SwiftUI NavigationView

I have two views - a MasterView and DetailView. When opening the DetailView, I initialise a new class that tracks data about the view (in the real implementation, the detail view involves a game).
However, when I press the back button from the DetailView to return to the MasterView, and then press the button to return to the DetailView, my class is unchanged. However, I would like to re-initialise a new copy of this class (in my case to re-start the game) whenever I move from the MasterView to the DetailView.
I have condensed the problem to this code:
import SwiftUI
import Combine
class Model: ObservableObject {
#Published var mytext: String = "mytext"
}
struct MasterView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(model: Model())) {
Text("press me")
}
}
}
}
struct DetailView: View {
#ObservedObject var model: Model = Model()
var body: some View {
TextField("Enter here", text: $model.mytext)
}
}
struct MasterView_Previews: PreviewProvider {
static var previews: some View {
MasterView()
}
}
I would like to create a new instance of Model every time I click the NavigationLink to the detail view, but it seems like it always refers back to the same original instance - I can see this by typing a change into the text field of the DetailView, which persists if I go back and forward again.
Is there any way of doing this?
Based on your comments - and correct me where wrong - here's how I'd set things up.
Your needs are:
A "base" class. Call it MasterView, "settings", "view state", whatever. This is where everything starts.
A "current game".... well, it could be a struct, a class, even properties in an ObservableObject.
I think that's about it. Hierarchically, your model could be:
ViewState
...Player
......Properties, including ID and history
...Current Game
...... Properties, including difficulty
Please note, I've changed some names and am being very vague on properties. The point is, you can encapsulate all of this in an ObservableObject, create an `EnvironmentObject of it, and have all your SwiftUI views "react" to changes in it.
Leaving out views, hopefully you can see where this "model" can contain just about all the Swift code you wish to do everything - now all you need is to tie in your views.
(1) Create your ObservableObject. It needs to (a) be a class object and (b) conform to the ObservableObject protocol. Here's a simple one:
class ViewState : ObservableObject {
var objectWillChange = PassthroughSubject<Void, Never>()
#Published var playerID = "" {
willSet {
objectWillChange.send()
}
}
}
You can create more structs/classes and instantiate them as needed in your model.
(2) Instantiate ViewState once min your environment. In SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView()
.environmentObject(ViewState())
)
self.window = window
window.makeKeyAndVisible()
}
}
Note that there's a single line added here and that ViewState is instantiated a single time.
(3) Finally, in any SwiftUI view that needs to know your view state, bind it by adding one line of code:
#EnvironmentObject var model: ViewState
If you want, you can do virtually anything in your model (ViewState) from instantiating a new game, flag something to result in a modal popup, add a player to an array, whatever.
The main thing I hope I'm explaining is there's no need to instantiate a second view state - rather instantiate a second game instance inside your single view state.
Again, if I'm way off from your needs, let me know - I'll gladly delete my answer. Good luck!

Resources