SwiftUI Navigate up down not working in Grid - swift

Here is my simple tvOS 16.0 app with the following code:
import SwiftUI
struct ContentView: View {
var sixColumnGrid: [GridItem] = Array(repeating: .init(.flexible()), count: 6)
let colors: [Color] = [.red, .green, .blue, .yellow, .cyan, .pink, .gray, .indigo, .brown]
var body: some View {
NavigationStack {
LazyVGrid(columns: sixColumnGrid) {
ForEach(colors, id: \.self) { color in
NavigationLink {
//MyView()
} label: {
Text(color.description.capitalized)
.padding()
.background(color)
}
.padding(20)
.buttonStyle(.card)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The code is pretty simple where I have a LazyVGrid with six column. The grid is populated with 9 different colors. Each item of the grid is a NavigationLink
This is how it renders:
The issue I am facing is that one can navigate with up and down keys between two rows there only if there is any item directly below or above the source item from where we are pressing the up/down key.
For instance, in the above code example, if we press up key when we are on Brown color it takes us to Blue color, and if we press down key when we are on Blue color, we can navigate to Brown Color.
But the issue is that when you are on Yellow color, you can't go down to the bottom row via Down key, you need to go left first, towards the Blue color and then you can go down to the second row via Down key.
Can you share how we can make navigation to go up/down even if the element is not directly above or down below that same item in the grid.
My goal is to have multiple grids like that in my view but should be able to go up and down between those grids even if the item is not directly the above item from where we are pressing up/down keys.
Is there any limitation with NavigationStack/LazyVGrid/NavigationLink that one can navigate with up/down keys only if any item is directly present in down/up direction of that specific item?
How can we navigate to different sections of the pages via up/down keys if there is such limitation? It is not practical to have all the items aligned vertically on a page to make them navigate through up and down keys.

Related

SwiftUI List on macOS: highlighting content in the selected row?

In a SwiftUI List, when a row is selected, a blue selection is drawn in the selection, and foreground Text with the default primary color is automatically made white. For other views with custom colors, I'd like to be able to make sure they become tinted white to match the system apps. For example, see the blue dot on the selected row:
Example code:
List(selection: $selection) {
ForEach(0..<4) { index in
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
Text("Test")
}
.tag(index)
}
}
Any tips on how to achieve the correct result here? 🙏
With NSTableCellView, there is the backgroundStyle property, but I couldn't find anything like this.
I've searched all of the available environment variables, and couldn't find anything appropriate.
I have also tried manually including an isSelected binding on a view for each row, but the selection binding is not updated by List until mouse-up, while the highlight is updated on mouse-down and drag, so this results in a flickery appearance and is not right either.
I'm also looking to customize not just a single SF Symbol image, but also a RoundedRectangle that is drawn with a custom foreground color that I want to become white upon selection.
Approach
Use a Label for the cell
Code
struct ContentView: View {
#State private var selection: Int?
var body: some View {
List(selection: $selection) {
ForEach(0..<4) { index in
Label("Test", systemImage: "circle.fill")
.tag(index)
}
}
}
}
Screenshot

SwiftUI - How to display paged tabs (via tabview) within a single view?

I'm trying to create a way of navigation via tabs that looks something like this:
where there are multiple tabs, and you can swipe to get to the one on the left, or tap it to achieve the same behavior. I thought maybe using TabView with .tabViewStyle(.page) could potentially lead me to this type of navigation:
#State var tabs: [String] = []
...
TabView {
ForEach(0 ..< days.count, id: \.self) { i in
ZStack {
Button(days[i]) {}
}
.frame(width: 100, height: 50)
}
ZStack {
Button("Add") {
withAnimation(.spring()) {
days.append("tab")
}
}
}
.frame(width: 100, height: 50)
}
.tabViewStyle(.page)
which gave me something like this:
Here, I have one tab that has a button "add" that adds other tabs "tab". But as you can see, you can only see one tab in the view at a time. I really like the "swiping" behavior of tabs, so I'm wondering is there a way to implement paged tab views such that multiple tabs can be seen at the same time, like in the picture above?

SwiftUI: Editable TextFields in Lists

*** NOTE: This question concerns macOS, not iOS ***
Context:
I have a list of items that serves as a "Master" view in a standard Master-Detail arrangement. You select an item from the list, and the detail view updates:
In SwiftUI, this is powered by a List like this:
struct RuleListView: View
{
#State var rules: [Rule]
#State var selectedRuleUUID: UUID?
var body: some View
{
List(rules, id: \.uuid, selection: $selectedRuleUUID) { rule in
RuleListRow(rule: rule, isSelected: (selectedRuleUUID == rule.uuid))
}
}
}
The Problem:
The name of each item in the list is user-editable. In AppKit, when using NSTableView or NSOutlineView for the list, the first click on a row selects that row and the second click would then begin editing the NSTextField that contains the name.
In SwiftUI, things have apparently gotten dumber. Clicking on ANY text in the list immediately begins editing that text, which makes selecting a row VERY difficult—you have to click on the 2 pixels above or below the TextField in each row.
To combat this, I've stashed a clear Button() on top of every row that's not selected. This button intercepts the click before it reaches the TextField() and selects the row. When the row is selected, the Button() is no longer included and subsequent clicks then select the TextField():
struct RuleListRow: View
{
var rule: Rule
var isSelected: Bool
var body: some View
{
ZStack
{
Label {
TextField("", text: $rule.name)
.labelsHidden()
} icon: {
Image(systemName: "lasso.sparkles")
}
if !isSelected
{
Button {
// No-op
} label: {
EmptyView()
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.clear)
.contentShape(Rectangle())
}
}
}
}
Question:
The above works, but...this can't be correct, right? I've missed something basic that makes List behave properly when it contains TextField rows, right? There's some magic viewModifier I'm supposed to stick somewhere, I'm sure.
What is the correct, canonical way to solve this issue using Swift 5.5 and targeting macOS 11.0+?
Note:
My first approach was to simply disable the TextField when the row isn't selected, but that caused the text to appear "dimmed" and I couldn't find a way to override the text color when the TextField is disabled.

Programmatically adding buttons in a list on Swift 5

So I'm relatively new to swift and coding in general, I'm currently trying to develop an app that essentially operates like a to do list. My goal for the home page is pretty basic. The logo centered at the top and a button to create "New Lists". I've gone back and forth on how to manage the various lists (using buttons or using listviews). But ideally I want the New List button to create a new button (which accesses the newly created list) and also take the user to the newly created list to name it and add contents etc.
I'm currently exploring the use of NavigationLinks and Navigation View (Code Below)
import SwiftUI
struct ContentView: View {
#State private var isShowingNewList = false
var body: some View {
NavigationView{
VStack(spacing: 30) {
NavigationLink(destination: Text("This is gonna be a new list"), isActive: $isShowingNewList) { EmptyView()}
Button("New List!!") {
self.isShowingNewList = true
}
.buttonStyle(.bordered)
.offset(y: -100)
}
.navigationBarTitle("Nudge")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm thinking navigation view is not the way to go because even when creating a button to control the link it controls the entire screen. Any pointers?
This is a complex question so any sort of guidance or suggestions of strategies that could be worth looking into would be great!

Remove default padding from List in SwiftUI

When using ScrollView the views inside it are spread across the whole screen width by default, but when using List, there is a padding on the sides. Is there a way to get rid of this padding?
To achieve this you need to use ForEach inside List combined with .listRowInsets as in example below
struct Demo: View {
var colors: [Color] = [.red, .blue, .yellow]
var body: some View {
List {
ForEach(colors, id: \.self) { color in
color
}.listRowInsets(EdgeInsets())
}
// Update: Different iOS versions have different
// default List styles, so set explicit one if needed
.listStyle(PlainListStyle())
}
}
For iOS 15.0+, you may try listStyle modifier and set it as plain.
var body: some View {
List {
// some rows
}
.listStyle(.plain)
}