SwiftUI - Make List Divider Line Expand To Touch Screen Edges - list

I am working with lists in SwiftUI. I cannot figure out how to make the line separator go edge to edge. I have tried .edgesIgnoringSafeArea, but that does not seem to do the trick. See image below:
Notice how the list lines touch the right edge, but not the left.

It doesn't seem there's proper solution to control the separator in particular, so you have to have your own workaround.
I would suggest to play with the EdgeInsets to expand the row to reach edges, so the separator.
Then you can add the padding you want to row's content.
List(0..<10) { _ in
HStack {
Text("title")
Spacer()
}.background(Color.gray)
.padding(.horizontal, 20)
}.padding(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: -20))
And the final result would be something like this:
You can also simplify it further by replacing
.padding(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: -20))
with
.padding(.horizontal, -20)
You could have different padding/Inset for each row, here's a code snippet from Apple's documentation for listRowInsets
struct ContentView: View {
enum Flavor: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case vanilla, chocolate, strawberry
}
var body: some View {
List {
ForEach(Flavor.allCases) {
Text($0.rawValue)
.listRowInsets(.init(top: 0,
leading: 25,
bottom: 0,
trailing: 0))
}
}
}
}

Related

How to change the background of item in foreach embedded in list while using onMove()?

Here's my code
`
List {
ForEach(timerListVM.editTimerList.timerList, id: \.self) { timer in
AddTimerRowView(timer: timer)
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 4, leading: 0, bottom: 8, trailing: 0))
}
.onMove { indexSet, index in
timerListVM.editTimerList.timerList.move(fromOffsets: indexSet, toOffset: index)
}
}
.listStyle(.plain)
.frame(maxWidth: .infinity)
`
I use listRowInsets() to put some space between item, but when I press on an item, it gets a white background and shadow.
how to hide the background and just make item wrapped by shadow?
You can use .padding(.all) or .padding().

SwiftUI NavigationItem dynamic data sending incorrect parameter

I have what I thought was a simple task, but it appears otherwise. As part of my code, I am simply displaying some circles and when the user clicks on one, it will navigate to a new view depending on which circle was pressed.
If I hard code the views in the NavigationLink, it works fine, but I want to use a dynamic array. In the following code, it doesn't matter which circle is pressed, it will always display the id of the last item in the array; ie it passes the last defined dot.destinationId to the Game view
struct Dot: Identifiable {
let id = UUID()
let x: CGFloat
let y: CGFloat
let color: Color
let radius: CGFloat
let destinationId: Int
}
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
.frame(width: dot.radius, height: dot.radius)
.position(x:dot.x, y: dot.y )
}
}
}
.frame(width: .infinity, height: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
struct Game: View {
var id: Int
var body: some View {
Text("\(id)")
}
}
I tried to send the actual view as i want to show different views and used AnyView, but the same occurred It was always causing the last defined view to be navigated to.
I'm really not sure what I a doing wrong, any pointers would be greatly appreciated
While it might seem that you're clicking on different dots, the reality is that you have a ZStack with overlapping views and you're always clicking on the top-most view. You can see when you tap that the dot with ID 6 is always the one actually getting pressed (notice its opacity changes when you click).
To fix this, move your frame and position modifiers outside of the NavigationLink so that they affect the link and the circle contained in it, instead of just the inner view.
Also, I modified your last frame since there were warnings about an invalid frame size (note the different between width/maxWidth and height/maxHeight when specifying .infinite).
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack {
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
}
.frame(width: dot.radius, height: dot.radius) //<-- Here
.position(x:dot.x, y: dot.y ) //<-- Here
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
}

SwiftUI re-orderable non full screen list

The intended goal is a View that expands in height to show all its elements (no scrolling), that can allow its elements to be drag and dropped to re ordered them. I believe a SwiftUI list will support this drag and drop behaviour however when placed in a VStack with other elements, it limits it's height to be the size of one element. I presume these List views aren't intended to be used within another view?
List {
ForEach(viewModel.sections, id: \.self) { section in
HStack {
Text(section.localisedString)
Spacer()
Image(systemName: "line.horizontal.3")
.foregroundColor(ColourPalette.bodyText)
.smallIcon()
}
.background(ColourPalette.background)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.background(ColourPalette.background)
}
.listStyle(PlainListStyle())
.background(ColourPalette.background)
I would also like to hide the separators however this appears to be an ongoing struggling with SwiftUI but even the SideBarListStyle() doesn't work for me.
Does it make more sense to use a VStack() and if so how easily can the reorder on drag drop and classic list side bar items be implemented?
You have tow options :
1) Using List
For placing the VStack with your List , use Section with header / footer, like that it will scroll with the list
List {
Section(header: Text("Your header here")) {
ForEach(viewModel.sections, id: \.self) { section in
HStack {
Text(section.localisedString)
Spacer()
Image(systemName: "line.horizontal.3")
.foregroundColor(ColourPalette.bodyText)
.smallIcon()
}
.background(ColourPalette.background)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.background(ColourPalette.background)
}
}
And for removidng the separator (IOS 13 support):
Adding UITableView.appearance().separatorColor = .clear anywhere in your code before the List appears should work. While this solution removes the separators, note that all List instances will be bound to this style as there’s no official way currently to only remove separators of specific instances. You may be able to run this code in onAppear and undo it in onDisappear to keep styles different.
2) Use VStack / LazyVStack

Hiding Picker's focus border on watchOS in SwiftUI

I need to use a Picker view but I don't see any options to hide the green focus border.
Code:
#State private var selectedIndex = 0
var values: [String] = (0 ... 12).map { String($0) }
var body: some View {
Picker(selection: $selectedIndex, label: Text("")) {
ForEach(0 ..< values.count) {
Text(values[$0])
}
}
.labelsHidden()
}
The following extension puts a black overlay over the picker border.
Result
Code
extension Picker {
func focusBorderHidden() -> some View {
let isWatchOS7: Bool = {
if #available(watchOS 7, *) {
return true
}
return false
}()
let padding: EdgeInsets = {
if isWatchOS7 {
return .init(top: 17, leading: 0, bottom: 0, trailing: 0)
}
return .init(top: 8.5, leading: 0.5, bottom: 8.5, trailing: 0.5)
}()
return self
.overlay(
RoundedRectangle(cornerRadius: isWatchOS7 ? 8 : 7)
.stroke(Color.black, lineWidth: isWatchOS7 ? 4 : 3.5)
.offset(y: isWatchOS7 ? 0 : 8)
.padding(padding)
)
}
}
Usage
Make sure .focusBorderHidden() is the first modifier.
Picker( [...] ) {
[...]
}
.focusBorderHidden()
[...]
On the Picker, something like this can be added to cover up the green border.
#ScaledMetric var borderWidth: CGFloat = 5 // or it can be 3
Picker {
...
}.border(Color.black, width: borderWidth)
Adding a mask of cornerRadius of whatever required but with a padding of 2 or over (so as to mask the outer edge of the view) as the green border width on the watch tends to be around 2... will do the trick
.mask(RoundedRectangle(cornerRadius: 12).padding(2))
I like the border method from Ray Hunter too but to keep things tidy and simple I rather stay away from having lots and lots of #... variables

Swiftui hide disclosure arrow

I have this View
NavigationView {
GeometryReader { geometry in
List {
ForEach(self.viewModel.items) { item in
HStack(spacing: 0, content: {
ZStack {
RowItemView(data: item.FirstItem)
NavigationLink(destination: CustomView(data: item.FirstItem))
{
EmptyView()
}
}
.frame(width: geometry.size.width / 2, alignment: .center)
if (item.SecondItem != nil) {
ZStack {
RowItemView(data: item.SecondItem!)
NavigationLink(destination: CustomView(data: item.SecondItem!))
{
EmptyView()
}
}
})
}.listRowInsets(EdgeInsets())
}
}
I want to hide the disclouse arrow of the NavigationView.
I try to add .buttonStyle(PlainButtonStyle()) or add a negative trailing to the navigationLink, but it doesn't change.
I have already read this question and this one but they do not work in my case, probably because I'm creating a grid and not a plain list.
In this scenario the possible approach is to use zero frame, as following
NavigationLink(destination: CustomView(data: item.FirstItem)) {
EmptyView()
}.frame(width: 0)
Thanks to #Asperi I figured out what is the problem.
With a zero EdgeInsets in listRowInsets the arrow still show.
So I create this trick (Works in Xcode 13.3.1)
List {
ForEach(self.viewModel.items) { item in
//code for creating the row
}.listRowInsets(EdgeInsets.init(top: 8, leading: 0, bottom: 8, trailing: 0))
Put a value for both top and bottom.
I do not know if it is the correct way to get rid of that annoying arrow, but for my app it works :-)