Reputation: 3282
I have this code to display a list of custom rows.
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
List(1...10) {_ in
CustomRow()
}
}
}
}
However, I want to remove the line on each row. I tried not using List
and instead using ForEach
inside ScrollView
but it completely removes all the styling including its padding and margins. I just want to remove the lines and nothing else.
Please help, thank you.
Upvotes: 195
Views: 143000
Reputation: 608
If you want to hide only the bottom separator, do something like this
Text("Hello, world!")
.listRowSeparator(.hidden, edges: [.bottom])
Upvotes: 7
Reputation: 11
For iOS 14
use CocoaUI: https://github.com/p-x9/CocoaUI
struct ListView : View {
var body: some View {
ZStack{
List{
ForEach(0..<10){
Text("Text \($0)")
}
}
.cocoa(onViewDidAppear: {
var config = UICollectionLayoutListConfiguration(appearance: .plain)
config.showsSeparators = false
let layout = UICollectionViewCompositionalLayout.list(using: config)
$0?.collectionViewLayout = layout
})
.listStyle(.plain)
}
}
}
Upvotes: 0
Reputation: 139
I have the same issue as you about handling separates line in List of SwiftUI.
To make it more simple, let's think about these case:
If your app target is:
ScrollView {
ForEach(datas, id: \.self) { item in
YourCustomRowView(data: item)
}
}
Just keep in mind that you can handle everything in ScrollView that List can handle (just do more a little bit)
List {
ForEach(self.datas) { item in
YourCustomRowView(data: item)
.listRowSeparator(.hidden)
}
}
Upvotes: 5
Reputation: 119917
This year Apple introduced a new modifier .listRowSeparator
that can be used to style the separators. you can pass .hidden
to hide it:
List {
ForEach(items, id:\.self) {
Text("Row \($0)")
.listRowSeparator(.hidden)
}
}
you may consider using a LazyVStack
inside a ScrollView
instead (because iOS is NOT supporting UIAppearance
for SwiftUI lists anymore).
⚠️ This method is deprecated and it's not working from iOS 14
There is a UITableView
behind SwiftUI's List
for iOS 13. So to remove
you need a tableFooterView
and to remove
you need separatorStyle
to be .none
init() {
if #available(iOS 14.0, *) {
// iOS 14 doesn't have extra separators below the list by default.
} else {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
}
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
Note that a static list doesn't show extra separators below the list by default
Upvotes: 311
Reputation: 357
This is my solution that contains all considerations:
let style: UITableViewCell.SeparatorStyle
public func body(content: Content) -> some View {
content
.introspectTableView { tableView in
tableView.separatorStyle = .none
}
}
}
public extension View {
func listSeparator(style: UITableViewCell.SeparatorStyle) -> some View {
ModifiedContent(content: self, modifier: ListSeparatorStyle(style: style))
}
}
Implemented:
List {
// code...
}
.listSeparator(style: .none)
Upvotes: -1
Reputation: 79
extension View {
func hideRowSeparator(insets: EdgeInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0),
background: Color = .white) -> some View {
modifier(HideRowSeparatorModifier(insets: insets, background: background))
}
}
struct HideRowSeparatorModifier: ViewModifier {
static let defaultListRowHeight: CGFloat = 44
var insets: EdgeInsets
var background: Color
init(insets: EdgeInsets, background: Color) {
self.insets = insets
var alpha: CGFloat = 0
if #available(iOS 14.0, *) {
UIColor(background).getWhite(nil, alpha: &alpha)
assert(alpha == 1, "Setting background to a non-opaque color will result in separators remaining visible.")
}
self.background = background
}
func body(content: Content) -> some View {
content
.padding(insets)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: Self.defaultListRowHeight)
.listRowInsets(EdgeInsets())
.overlay(
VStack {
HStack {}
.frame(maxWidth: .infinity)
.frame(height: 1)
.background(background)
Spacer()
HStack {}
.frame(maxWidth: .infinity)
.frame(height: 1)
.background(background)
}
.padding(.top, -1)
)
}
}
struct ContentView: View {
var body: some View {
List {
ForEach(0 ..< 30) { item in
HStack(alignment: .center, spacing: 30) {
Text("Hello, world!:\(item)").padding()
}
.hideRowSeparator(background: .white)
}
}
.listStyle(PlainListStyle())
}
}
Upvotes: 4
Reputation: 239
For iOS 14:
As .listRowSeparator(.hidden)
is available only for iOS 15, you can hide separator in lower versions by setting edgeinsets as 0 explicitly.
Content View:
List {
ForEach(viewModel.items, id: \.id) { item in
YourListItem(item)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
Accompany above change, by making background of row item to white (or color of root page)
Row Item:
var body: some View {
VStack {
..... your content
}
.background(Colors.white)
}
VStack is just example. It can be any component.
Upvotes: 1
Reputation: 1
I have the same problem. But I know a handmade solution for it. So, if you set List row parameters like:
.listRowInsets(EdgeInsets(top: -5, leading: 0, bottom: 0, trailing: 0))
and padding of row view body like
.padding(EdgeInsets(top: Statics.adjustValue(v: 10), leading: Statics.adjustValue(v: 10), bottom: Statics.adjustValue(v: 10), trailing: Statics.adjustValue(v: 10)))
then separators will be hidden.
FOR ALL iOS VERSIONS
Upvotes: -1
Reputation: 1
It is possible to just use negative insets and solid color to mask over the separator.
Upvotes: 0
Reputation: 1758
Simply add .listRowSeparator(.hidden)
as a modifier to the view contained in the List
. https://developer.apple.com/documentation/swiftui/texteditor/listrowseparator(_:edges:)
List {
ForEach(garage.cars) { car in
Text(car.model)
.listRowSeparator(.hidden)
}
}
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.
Also note that this code assumes Apple is using a UITableView
to back List
which is not true in the iOS 14 SDK. Hopefully they add an official API in the future. Credit to https://twitter.com/singy/status/1169269782400647168.
Upvotes: 92
Reputation: 1450
A simple solution that will work on both iOS 13 and 14
extension List {
func removeSeparator() -> some View {
if #available(iOS 14.0, *) {
return self.listStyle(SidebarListStyle()).erasedToAnyView()
} else {
return self.onAppear {
UITableView.appearance().separatorStyle = .none
}.erasedToAnyView()
}
}
Upvotes: -1
Reputation: 14063
This seems to be the only thing that works for me.
List() {
}
.listStyle(SidebarListStyle())
Upvotes: 3
Reputation: 7
You can remove the dividers by setting the listStyle to InsetListStyle()
, like this:
.listStyle(InsetListStyle())
Add this to the end of your code
Upvotes: -1
Reputation: 10839
For iOS 14 you have this :
.listStyle(SidebarListStyle()) # IOS 14
Upvotes: 8
Reputation: 4565
Remove paddings and separator
iOS 14.2, Xcode 12.2
ScrollView {
LazyVStack {
ForEach(viewModel.portfolios) { portfolio in
PortfolioRow(item: portfolio)
}
}
}
This gives you complete control over the list. Current implementation of List doesn't provide full control and contains some issues.
Upvotes: 3
Reputation: 181
I'm not sure if you need all the functionality of "UITableView" in SwiftUI, but if you just want to just display a list of views in iOS 13 or later couldn't you just do:
ScrollView {
VStack(alignment: .leading) {
ForEach(1...10) { _ in
CustomRow()
}
}
}
And then add .padding()
for any margins you want?
Upvotes: 0
Reputation: 10839
This is my extension ListRowExtensions for hide list row separator and custom this one.
import SwiftUI
// MARK: List row extensions
extension View {
func hideListRowSeparator() -> some View {
return customListRowSeparator(insets: .init(), insetsColor: .clear)
}
func customListRowSeparator(
insets: EdgeInsets,
insetsColor: Color) -> some View {
modifier(HideRowSeparatorModifier(insets: insets,
background: insetsColor
)) .onAppear {
UITableView.appearance().separatorStyle = .none
UITableView.appearance().separatorColor = .clear
}
}
}
// MARK: ViewModifier
private struct HideRowSeparatorModifier: ViewModifier {
var insets: EdgeInsets
var background: Color
func body(content: Content) -> some View {
content
.padding(insets)
.frame(
minWidth: 0,
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .leading
)
.listRowInsets(EdgeInsets())
.background(background)
}
}
Use :
// Without list row separator
List {
ForEach(self.viewModel.data, id: \.id) { item in
Text("Text")
}
.hideRowSeparatorItemList()
}
// With list row separator with color and size
List {
ForEach(self.viewModel.data, id: \.id) { item in
Text("Text")
}
.customListRowSeparator(insets: EdgeInsets(top: 0,
leading: 0,
bottom: 5,
trailing: 0),
insetsColor: Color.red)
}
Upvotes: 0
Reputation: 395
All the answers tell you to use ScrollView (which is what I recommend too)
But in case you want to use List and want to remove the separator lines..
Install the SwiftPM: https://github.com/siteline/SwiftUI-Introspect
SAMPLE:
List {
Text("Item 1")
Text("Item 2")
}
.introspectTableView { tableView in
tableView.separatorStyle = .none
}
Upvotes: 4
Reputation: 155
I started a project to solve this in iOS14 since the iOS 13 workarounds no longer work. It allows setting the separator style, separator color, and separator inset.
Hide separators on the List
List { <content> }
.listSeparatorStyle(.none)
Show a single divider line with configurable color and insets
List { <content> }
.listSeparatorStyle(.singleLine, color: .red, inset: EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 20)
https://github.com/SchmidtyApps/SwiftUIListSeparator
Upvotes: 3
Reputation: 322
IOS 14
There is currently no solution to hide the separators on the IOS 14 beta.
If you don't need an editable List
, you should use a LazyVStack
inside a ScrollView
.
But if you want to stay on the List
. I found a solution on the Apple forum by samwarner. https://developer.apple.com/forums/thread/651028
This is a temporary solution. In some cases you may need to adjust the insets. Here is its implementation:
struct HideRowSeparatorModifier: ViewModifier {
static let defaultListRowHeight: CGFloat = 44
var insets: EdgeInsets
var background: Color
init(insets: EdgeInsets, background: Color) {
self.insets = insets
var alpha: CGFloat = 0
UIColor(background).getWhite(nil, alpha: &alpha)
assert(alpha == 1, "Setting background to a non-opaque color will result in separators remaining visible.")
self.background = background
}
func body(content: Content) -> some View {
content
.padding(insets)
.frame(
minWidth: 0, maxWidth: .infinity,
minHeight: Self.defaultListRowHeight,
alignment: .leading
)
.listRowInsets(EdgeInsets())
.background(background)
}
}
extension EdgeInsets {
static let defaultListRowInsets = Self(top: 0, leading: 16, bottom: 0, trailing: 16)
}
extension View {
func hideRowSeparator(insets: EdgeInsets = .defaultListRowInsets, background: Color = .white) -> some View {
modifier(HideRowSeparatorModifier(insets: insets, background: background))
}
}
Finally, here is the implementation on a list. You have to add .hideRowSeparator()
on the list cell.
struct CustomRow: View {
let text: String
var body: some View {
HStack {
Text(self.text)
Image(systemName: "star")
}
}
}
struct ContentView : View {
@State private var fruits = ["Apple", "Orange", "Pear", "Lemon"]
var body: some View {
VStack(alignment: .leading) {
List {
ForEach(self.fruits, id: \.self) { str in
CustomRow(text: str)
.hideRowSeparator()
}
}
}
.padding(.top)
}
}
Upvotes: 11
Reputation: 204
Doing something like:
UITableView.appearance().separatorColor = .clear
works, but in a lot of cases is not something that I would recommend. These are global changes - i.e. they will affect all instances of UITableView. This is a problem if you have multiple UITableViews that want different styles. Or if you are developing a framework, clients using your framework will inherit those changes too!
A safer solution is to only target UITableViews that live inside a specified container. Luckily the appearance
api gives us a way to be specific:
UITableView.appearance(whenContainedInInstancesOf: [UIHostingController<YourSwiftUiViewHere>.self]).separatorColor = .clear
Upvotes: 8
Reputation: 310
While these answers are technically correct they will affect a List
or Form
globally(across the entire app) from my experience.
A hacky way I found to resolve this problem, at least in my app, is to add the following code to the "main" content view of the app:
.onAppear(perform: {
UITableView.appearance().separatorStyle = .none
})
Then on any other view that you want to the separator lines add this to the end of the List
or Form
view
.onAppear(perform: {
UITableView.appearance().separatorStyle = .singleLine
})
This seems to add the single line separator to any view sheet that is above the main content view. Again this is all anecdotal to my recent SwiftUI experience.
In my experience I only had to add the .onAppear(... = .singleLine)
method to one of my "detail" views and the separator line appeared on all subsequent views that were presented.
Edit: Another note as this answer continues to gain attention. This solution I posted doesn't solve all cases, it certainly did not solve it for me, again in some cases. I ended up using Introspect for SwiftUI to solve this problem across the entire app.
I hope this clears up some confusion and frustration as people come across this post.
Upvotes: 10
Reputation: 1684
Check out SwiftUI-Introspect. It exposes the underlying UIKit/AppKit views.
In this case you could manipulate the UITableView directly (without having to change all table views via the appearance proxy):
import Introspect
:
:
List {
...
}.introspectTableView { tableView in
tableView.separatorStyle = .none
}
Upvotes: 11
Reputation: 5644
Use a ScrollView?
Some state that represents your list
@State var menuItems: [String] = ["One", "Two", "Three"]
The a SwiftUI
ForEach
loop inside a ScrollView
ScrollView {
ForEach(self.menuItems, id: \.self) { item in
Text(item)
}
}
Upvotes: 4