`focusable()` broken for views that are not already focusable - swiftui

This is taken from a completely fresh multiplatform SwiftUI project. I want to be able to make a custom view focusable, so I tried testing with a Text view but it's not working. Isn't that the whole point of .focusable() to declare an arbitrary view focusable? If that's the case, then why isn't it working in the below code:
import SwiftUI
struct ContentView: View {
#FocusState private var focusedItem: Optional<FocusedItem>
var body: some View {
VStack {
Text("Test")
.focusable(true)
.focused($focusedItem, equals: .two)
Button("Toggle Focus") {
self.focusedItem=(self.focusedItem == nil) ? .two : nil
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// Always prints `nil`.
print(self.focusedItem)
}
}
}
}
enum FocusedItem {
case one, two
func other() -> FocusedItem {
switch self {
case .one: return .two
case .two: return .one
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

How to show in SwiftUI the sidebar in iPad and portrait mode

I have an master detail app in iPad, and when run the app in portrait mode the sidebar is hidden. I need to push Back button to open the sidebar.
Can anyone help me to show the sidebar by default?
I found an answer that suggest to use StackNavigationViewStyle when the app is in portrait, but then the app seems like a giant iPhone and dissapears the master class like a sidebar to appear like a view.
Thats my code.
struct ContentView: View {
var body: some View {
NavigationView {
MyMasterView()
DetailsView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MyMasterView: View {
var people = ["Option 1", "Option 2", "Option 3"]
var body: some View {
List {
ForEach(people, id: \.self) { person in
NavigationLink(destination: DetailsView()) {
Text(person)
}
}
}
}
}
struct DetailsView: View {
var body: some View {
Text("Hello world")
.font(.largeTitle)
}
}
Thank you
It can be done, but for now it requires access to UIKit's UISplitViewController via UIViewRepresentable. Here's an example, based on a solution described here.
import SwiftUI
import UIKit
struct UIKitShowSidebar: UIViewRepresentable {
let showSidebar: Bool
func makeUIView(context: Context) -> some UIView {
let uiView = UIView()
if self.showSidebar {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(of: UISplitViewController.self)?
.show(.primary)
}
} else {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(of: UISplitViewController.self)?
.show(.secondary)
}
}
return uiView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(
of: UISplitViewController.self)?
.show(showSidebar ? .primary : .secondary)
}
}
}
extension UIResponder {
func next<T>(of type: T.Type) -> T? {
guard let nextValue = self.next else {
return nil
}
guard let result = nextValue as? T else {
return nextValue.next(of: type.self)
}
return result
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink("Primary view (a.k.a. Sidebar)", destination: DetailView())
}
NothingView()
}
}
}
struct DetailView: View {
var body: some View {
Text("Secondary view (a.k.a Detail)")
}
}
struct NothingView: View {
#State var showSidebar: Bool = false
var body: some View {
Text("Nothing to see")
if UIDevice.current.userInterfaceIdiom == .pad {
UIKitShowSidebar(showSidebar: showSidebar)
.frame(width: 0,height: 0)
.onAppear {
showSidebar = true
}
.onDisappear {
showSidebar = false
}
}
}
}
In iOS 16 you'll be able to control it using columnVisibility

Building navigation between Views in my iOS app

As the time flies, by App get more and more complicated shape.
In some cases the App flow might be:
View A -> View B -> C -> D
and then back
D -> C -> B -> A..
but sometimes i need to skip the view and go
D -> B -> A..
In some cases its A -> C -> D and then D -> A
I started to use NavigationView/NavigationLink and in some cases i use the following approach:
let weekView = WeekView(journey: journey, isRoot: isRoot).environmentObject(self.thisSession)
window?.rootViewController = UIHostingController(rootView: weekView)
No i realize that it has become a complete mess.. It's time for me to rethink this..
How do you handle navigation in apps where it can't be always done by pushing/popping the views from Navigation stack?
Using ViewBuilders is a good option here.
#ViewBuilder func myViewRouter(selection: Selection) -> some View {
switch selection {
case selection1:
View1()
case selection2:
View2()
case selection3:
View3()
}
}
enum Selection { ... }
ViewBuilders are powerful, its pretty much a function that can return opaque types but notice the lack of the return keyword. This seems like a perfect use case for it. In the example I used a enum but its also common to see this used with a var selection = 0 on the parent view and have the ViewBuilder as the child. Either way, same functionality.
Below is is a good url to understand ViewBuilders.
https://swiftwithmajid.com/2019/12/18/the-power-of-viewbuilder-in-swiftui/
Last edit: Here is an example use case:
import SwiftUI
struct ContentView: View {
#State private var selection: SelectionEnum = .zero
var body: some View {
VStack {
showMyViews(selection: selection)
HStack {
ForEach(SelectionEnum.allCases, id: \.self) { selection in
Button(action: {self.selection = selection}){
Text(selection.rawValue)
.fontWeight(.bold)
.frame(width: 60, height: 60)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
}
}
}
}
}
#ViewBuilder func showMyViews(selection: SelectionEnum) -> some View {
switch selection {
case .zero:
ViewA()
case .one:
ViewB()
case .two:
ViewC()
case .three:
ViewD()
}
}
enum SelectionEnum: String, CaseIterable {
case zero
case one
case two
case three
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ViewA: View {
var body: some View {
Text("View A")
}
}
struct ViewB: View {
var body: some View {
Text("View B")
}
}
struct ViewC: View {
var body: some View {
Text("View C")
}
}
struct ViewD: View {
var body: some View {
Text("View D")
}
}

How to display another view from an existing view in SwiftUI

I have an existing view displayed. After displaying that view for 2 seconds, I want to navigate or display another view. The following code does not work.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
UIGraphicsBeginImageContext(self.view.frame.size)
UIImage(named: "ProfileSplashScreen")?.draw(in: self.view.bounds)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.view.backgroundColor = UIColor(patternImage: image)
DispatchQueue.main.asyncAfter(deadline: .now()+2.0) {
GameSelectorController()
}
//=====================================================
import SwiftUI
struct GameSelectorController: UIViewController {
var body: some View {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
}
struct GameSelectorController_Previews: PreviewProvider {
static var previews: some View {
GameSelectorController()
}
}
In pure SwiftUI you would do something like this:
struct ContentView: View {
#State private var showFirst = true
var body: some View {
Group {
if showFirst {
StartView()
} else {
SecondView()
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.showFirst = false
}
}
}
}
struct StartView: View {
var body: some View {
Text("First")
}
}
struct SecondView: View {
var body: some View {
Text("Second")
}
}

How to update a view depending on button click from other view in SwiftUI

I often face this situation but so far I could not find a good solution. Thing is when my SwiftUI View gets bigger I refactor the code by making another struct and call the struct in the respective view. Say I got a struct A and I refactor some code in struct B, but how can I update the view or call a function in struct A depending on button click on struct B ? The below code might help to understand the situation:
import SwiftUI
struct ContentView: View {
#State var myText: String = "Hello World"
#State var isActive: Bool = false
var body: some View {
VStack {
Text(self.myText)
AnotherStruct(isActive: $isActive)
}
.onAppear {
if self.isActive == true {
self.getApi()
}
}
}
func getApi() {
print("getApi called")
self.myText = "Hello Universe"
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct AnotherStruct: View {
#Binding var isActive: Bool
var body: some View {
VStack {
Button( action: {
self.isActive.toggle()
}) {
Text("Button Tapped")
}
}
}
}
Here is a demo of possible approach to solve such cases - with separated responsibility and delegated activity.
struct ContentView: View {
#State var myText: String = "Hello World"
var body: some View {
VStack {
Text(self.myText)
AnotherStruct(onActivate: getApi)
}
}
func getApi() {
print("getApi called")
self.myText = "Hello Universe"
}
}
struct AnotherStruct: View {
let onActivate: () -> ()
// #AppStorage("isActive") var isActive // << possible store in defaults
var body: some View {
VStack {
Button( action: {
// self.isActive = true
self.onActivate()
}) {
Text("Button Tapped")
}
}
// .onAppear {
// if isActive {
// self.onActivate()
// }
// }
}
}

How to disable NavigationView push and pop animations

Given this simple NavigationView:
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
NavigationLink("Push Me", destination: Text("PUSHED VIEW"))
}
}
}
}
Did anyone find a way of disabling the NavigationView animation when a destination view is pushed/popped into/from the stack?
This has been possible in UIKit since iOS2.0! I think it is not too much to ask from the framework. I tried all sorts of modifiers on all views (i.e., the NavigationView container, the destination view, the NavigationLink, etc)
These are some of the modifiers I tried:
.animation(nil)
.transition(.identity)
.transaction { t in t.disablesAnimations = true }
.transaction { t in t.animation = nil }
None made a difference. I did not find anything useful in the EnvironmentValues either :-(
Am I missing something very obvious, or is the functionality just not there yet?
Xcode 11.3:
Right now there is no modifier to disable NavigationView animations.
You can use your struct init() to disable animations, as below:
struct ContentView : View {
init(){
UINavigationBar.setAnimationsEnabled(false)
}
var body: some View {
NavigationView {
VStack {
NavigationLink("Push Me", destination: Text("PUSHED VIEW"))
}
}
}
}
First you need state for the NavigationLink to respond to, then set that state inside a transaction with animations disabled, as follows:
struct ContentView : View {
#State var isActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink(isActive: $isActive, destination: {
Text("PUSHED VIEW")}) {
Text("Push Me")
}
Button("Navigate Without Animation") {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
isActive = true
}
}
}
}
}
}
I recently created an open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack) that contains the NavigationStackView, a view that mimics the navigation behaviours of the standard NavigationView adding some useful features. For example, you could use the NavigationStackView and disable the transition animations as requested by Kontiki in the question. When you create the NavigationStackView just specify .none as transitionType:
struct ContentView : View {
var body: some View {
NavigationStackView(transitionType: .none) {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
PushView(destination: View2()) {
Text("PUSH")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
PopView {
Text("POP")
}
}
}
}
PushView and PopView are two views that allow you push and pop views (similar to the SwiftUI NavigationLink). Here is the complete example:
import SwiftUI
import NavigationStack
struct ContentView : View {
var body: some View {
NavigationStackView(transitionType: .none) {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
PushView(destination: View2()) {
Text("PUSH")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
PopView {
Text("POP")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The result is:
It would be great if you guys joined me in improving this open source project.