Handling focus event changes on tvOS in SwiftUI - swiftui

How do I respond to focus events on tvOS in SwiftUI?
I have the following SwiftUI view:
struct MyView: View {
var body: some View {
VStack {
Button(action: {
print("Button 1 pressed")
}) {
Text("Button 1")
}.focusable(true) { focused in
print("Button 1 focused: \(focused)")
}
Button(action: {
print("Button 2 pressed")
}) {
Text("Button 2")
}.focusable(true) { focused in
print("Button 2 focused: \(focused)")
}
}
}
Clicking either of the buttons prints out correctly. However, changing focus between the two buttons does not print anything.
This guy is doing the same thing with rows in a list & says it started working for him with the Xcode 11 GM, but I'm on 11.5 and it's definitely not working (at least not for Buttons (or Toggles - I tried those too)).
Reading the documentation, this appears to be the correct way to go about this, but it doesn't seem to actually work. Am I missing something, or is this just broken?

In case anybody else stumbles upon this question, the answer is to make your view data-focused
So, if you have a list of movies in a scrollview, you would add this to your outer view:
var myMovieList: [Movie];
#FocusState var selectedMovie: Int?;
And then somewhere in your body property:
ForEach(0..<myMovieList.count) { index in
MovieCard(myMovieList[i])
.focusable(true)
.focused($selectedMovie, equals: index)
}
.focusable() tells the OS that this element is focusable, and .focused tells it to make that view focused when the binding variable ($selected) equals the value passed to equals: ...
For example on tvOS, if you wrap this in a scrollview and press left/right, it will change the selection and update the selected index in the $selectedMovie variable; you can then use that to index into your movie list to display extra info.
Here is a more complete example that will also scale the selected view:
https://pastebin.com/jejwYxMU

Related

SwiftUI - How to make navigation bar title editable (without changing any other behavior)?

I really like the look of the navigation bar title in SwiftUI, and I like that it appears just below the safe area, but appears in the principal part of the toolbar when you scroll down. I'm wondering how to completely replicate this look and behavior but make it editable by the user (most likely through a textfield?)
I've tried
.toolbar {
ToolbarItem(placement: .principal) {
TextField("Navigation Title", text: $mainTitle)
}
}
But this simply places the title in the toolbar at all times, rather than only when you scroll slightly.
Any ideas?
First I explain why your code does not work:
Only the size of the navigationTitle changes when you start to scroll, not the size of the whole toolbar or its items.
But I think I have a solution:
import SwiftUI
struct ContentView: View {
#State private var title: String = "Title"
#State private var titleSmall: Bool = false
var body: some View {
NavigationView {
List {
GeometryReader { geo in
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
.onChange(of: geo.frame(in: .global).minY) { val in
if val <= 53.5 {
titleSmall = true
} else {
titleSmall = false
}
}
}
Text("Hello, world!")
}
.toolbar {
ToolbarItem(placement: .principal) {
TextField("Title", text: $title)
.multilineTextAlignment(.center)
.font(titleSmall ? .headline : .largeTitle.bold())
.accessibilityAddTraits(.isHeader)
}
}
}
}
}
What the code does is: It gets the top Y position from the first (in this example) list item.
Then it checks if the first list item is under the title bar and changes the font size of the title if necessary.
The only Problem I see is that there is a pretty rough transition between small and big title but I think you can figure out how to fix this.
If you have more questions how the code works just ask
I hope that solves your question.
And I would recommend to have a look at Paul Hudson’s video about the Geometry Reader (he’s a great YouTuber): https://youtu.be/kh9lnIYgW1E
I just realized that it says „OLD“ in the video title, so it may be outdated.
But he has some other videos about the Geometry Reader.
just search for „Paul Hudson Geometry Reader“

SwiftUI: Change navigation bar title in the more tab?

I am begining to get my haead around swiftUI. So I have a simple tabView inside a Navigation View as below.
import SwiftUI
struct BasicView: View {
var climbList: [ClimbDetail]
var body: some View {
NavigationView{
VStack{
Text("").navigationBarTitle("Climbers Log", displayMode:.inline)
TabView {
Text("Search").tabItem{
Image(systemName:"magnifyingglass")
Text("Search")
}
Text("Stats").tabItem{
Image(systemName:"list.dash")
Text("Stats")
}
ClimbList(climbs: climbList).tabItem{
Image(systemName: "square")
Text("Climbs")
}
Text("Log").tabItem{
Image(systemName:"square.and.pencil")
Text("Log")
}
Text("Profile").tabItem{
Image(systemName:"person.circle")
Text("Profile")
}
Text("Settings").tabItem{
Image(systemName:"gear")
Text("Settings")
}
Text("Add Climb").tabItem{
Image(systemName:"plus")
Text("Add Climb")
}
}
}
}
}
}
Genrally it works as expected, however as I have 7 tabs it defults to the 'More' tab for the 5th tab. This is fine and good for the user.
However my issue is when you click the 'More' tab you get the a title bar and edit button with 'More' as the title. Which appears below the title bar I have set above.
So my question is how can I hide my titleBar when the user is on the 'More' tab and only show it inside the other tabs?
First off all, thanks for the question. I didn't know Apple provides More page by default for too long TabBars. I always needed that.
Back to you question, you just need to rearrange things. First off all, make the TabBar top level. Then a NavigationView and here comes your content. Later on you might outsource every view, to an own file.
TabView {
NavigationView
{
VStack
{
Text("Search")
}
.navigationBarTitle("Climbers Log", displayMode:.inline)
}.tabItem {
Image(systemName:"magnifyingglass")
Text("Search")
}
Every view takes it own NavigationView.. and then it works.

SwiftUI on MacOS: Call a function when NavigationLink Clicked

My need is simple but I can't seem to figure a way (and many posts are rather complicated or are old/for iOS).
I have a NavigationView with NavigationLink and it all works fine. For certain reasons, I'd like to know the item the user clicked on because it's used for other actions on the view.
I tried putting a button inside the NavigationView but then only the button action fires, but the link doesn't work.
I tried OnTapGesture - then the function fires but isn't propagated so the detail view never shows up.
What am I missing? Is there some way I can do this without overcomplicating?
ForEach(anArray, id: \.self) { entry in
NavigationLink(destination: SettingsView(s: entry)
{
Text("something")
}.onTapGesture {print("Hello")}
}
Here is the solution, actually use a button that does some action and activates a link
Tested with Xcode 11.5b2
#State private var activatedLink: Int? = nil
// ... other code
ForEach(Array(anArray.enumerated()), id: \.element) { (i, entry) in
Button("something") {
print(">> some action")
self.activatedLink = i // << order might be important !!
}.background(
NavigationLink(destination: SettingsView(s: entry), tag: i, selection: $activatedLink) { EmptyView() }
.buttonStyle(PlainButtonStyle()) // << required !!
)
}

SwiftUI Form Picker only shows once

I am playing around with SwiftUI, and am currently building a Form using a Picker.
import SwiftUI
struct ContentView: View {
private let names = ["Bill", "Peter", "Johan", "Kevin"]
#State private var favoritePerson = "Bill"
var body: some View {
NavigationView {
Form {
Picker("Favorite person", selection: $favoritePerson) {
ForEach(names, id: \.self) { name in
Text(name)
}
}
}
.navigationBarTitle("Form", displayMode: .inline)
}
}
}
The first time you tap on the "Favorite person" row, the picker shows up fine, and tapping on one of the names brings you back to the form. But tapping on the form row a second time doesn't do anything: you don't go to the picker, the row stays highlighted but nothing happens. If this is a SwiftUI bug, is there a known workaround? (I already needed to use a small navigation bar title to work around the Picker UI bug where otherwise its content moves up when it's shown ☹️)
this issue is just one with the simulator. If you build the app on a physical iOS device, it no longer becomes an issue. It's like that bug with Navigation Link that would only work once.
I have the same problem in Xcode 11.4 and also in the real device.
Picker change didn't call CreateTab, it only worked in initialize.
Picker("Numbers", selection: $selectorIndex) {
ForEach(0 ..< formData.tabs.count) { index in
Text(formData.tabs[index].name).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.onReceive([self.selectorIndex].publisher.first()) { (value) in
print(value)
CreateTab(tabs: formData.tabs, index: self.selectorIndex)
}

Toggle a button on Navigation Bar in SwiftUI & have it change appearance

Using SwiftUI, I would like to have the ability to change the button on my NavigationView based upon some Bool value indicating if it should be On or Off.
This would behave similar to how with UIKit you can replace a bar button item on either side of the screen to show a different button & associated action upon clicking.
I am able to get it working with the following code, but I am not certain if this is the best way to accomplish it, so am open to improvement.
import SwiftUI
struct HomeList: View {
#State var isOn = true
var body: some View {
NavigationView {
List(1 ..< 4) { index in
Text("Row \(index)")
}
.navigationBarTitle(Text(verbatim: "Title"), displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.isOn = !self.isOn
}, label: {
Text(self.isOn ? "On" : "Off")
})
)
}
}
}
The key pieces being:
Using the #State modifier on my isOn variable, which tells my interface to invalidate & re-render upon changes
Having my Button action modify isOn &it can also support other actions if I need
The ternary operator in my Button label that updates the Text (or an Image if I want) to reflect the correct appearance
Finally, how it appears in action: