Reputation: 4962
I'm trying to recreate an UI I built with UIKit in SwiftUI but I'm running into some minor issues.
I want the change the color of the List
here, but no property seems to work as I expects. Sample code below:
struct ListView: View {
@EnvironmentObject var listData: ListData
var body: some View {
NavigationView {
List(listData.items) { item in
ListItemCell(item: item)
}
.content.background(Color.yellow) // not sure what content is defined as here
.background(Image("paper-3")) // this is the entire screen
}
}
}
struct ListItemCell: View {
let item: ListItem
var body: some View {
NavigationButton(destination: Text(item.name)) {
Text("\(item.name) ........................................................................................................................................................................................................")
.background(Color.red) // not the area I'm looking for
}.background(Color.blue) // also not the area I'm looking for
}
}
Upvotes: 162
Views: 165182
Reputation: 992
The following solution changes all sorts of colors in SwiftUI's List
without changing the global appearance()
of the UITableView
. You can change the cell background as well as the List background.
import SwiftUI
struct ContentView: View {
var listItems: [String] = ["A", "B", "C"]
var body: some View {
List {
ForEach(listItems, id: \.self) { item in
Text(item)
}
.listRowBackground(Color.purple) // Applying the list cell background color
}
.scrollContentBackground(.hidden) // Hides the standard system background of the List
.background(Color.teal) // Applying list background color
}
}
Upvotes: 1
Reputation: 55655
iOS 16 provides a modifier to control the background visibility of List (and other scrollable views): scrollContentBackground(_:)
You can hide the standard system background via .hidden
. If you provide a background
as well, that will become visible.
List {
Text("One")
Text("Two")
}
.background(Image("MyImage"))
.scrollContentBackground(.hidden)
You may also want to customize the background of list rows - the individual cells - and separators. This can be done like so:
List {
Section("Header") {
Text("One")
Text("Two")
.listRowBackground(Color.red)
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
.scrollContentBackground(.hidden)
Upvotes: 74
Reputation: 1
The solution that works for me
List($someData, id: \.id) { item in
CellView(item: item)
.listRowBackground(Some Row Background Color) // color of the rows
}
.scrollContentBackground(.hidden) // should be set to .hidden to be able to set custom color
.background(Some List Background Color) // set the color of the list itself
Upvotes: 0
Reputation: 2295
For iOS 15 or earlier, doing this once
UITableView.appearance().backgroundColor = .clear
makes the .background view modifier work on any list after that. So in my app I just do
@main
struct MyApp: App
{
init()
{
// Work around SwiftUI's ridiculous lack of a way to set a list view's background color.
// Oddly, this will enable the .background view modifier to work on lists from now on.
UITableView.appearance().backgroundColor = .clear
}
...
}
After that, in any SwiftUI view you can do:
List(msgs) { aMsg in
Text(aMsg.senderID)
}
.background(Color.red)
Upvotes: 1
Reputation: 1771
2024 answer, they seem to change the List implementation often. In fact, it used to use a TableView now it uses a CollectionView. edgesIgnoringSafeArea
is what made it work for me.
List {
...
}
.modifier(ListBackgroundModifier())
struct ListBackgroundModifier: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if #available(iOS 16.0, *) {
content
.scrollContentBackground(.hidden)
.background(Color(UIColor.background).edgesIgnoringSafeArea(.all))
} else {
content
}
}
}
OR if using .listStyle(InsetListStyle())
you can just do this on each item in the list: .listRowBackground(Color(UIColor.background).edgesIgnoringSafeArea(.all))
Upvotes: 6
Reputation: 2197
List {
ForEach(dataArray, id:\.self) { item in
Text("\(item)")
.font(.title3)
}
.listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 0)) // EdgeInsets
.listRowBackground(Color.clear) // Change Row Color
.listRowSeparator(.hidden) //hide Seprator
}
.listStyle(.plain) //Change ListStyle
.scrollContentBackground(.hidden)
.background(Color.yellow) //Change Background Color
Upvotes: 12
Reputation: 121
Before and After modifiers applied
iOS 16
struct SomeView: View {
var body: some View {
ZStack {
List {
ForEach(0...5, id: \.self) { i in
Text("Sample text")
} //: LOOP
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
} //: LIST
.scrollContentBackground(.hidden)
} //: ZSTACK
.background(
Color.yellow.ignoresSafeArea(.all)
)
}
}
Upvotes: 1
Reputation: 33
Changing background did not work for me, because of the system background. I needed to hide it.
List(examples) { example in
ExampleRow(example: example)
}.background(Color.white.edgesIgnoringSafeArea(.all))
.scrollContentBackground(.hidden)
Upvotes: 2
Reputation: 11416
The following code makes ALL OF List
s background color transparent:
// Removes background from List in SwiftUI
extension NSTableView {
open override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
backgroundColor = NSColor.clear
if let esv = enclosingScrollView {
esv.drawsBackground = false
}
}
}
..........
..........
..........
the following code makes ALL OF TextEditor
s background color transparent:
extension NSTextView {
open override var frame: CGRect {
didSet {
backgroundColor = .clear
drawsBackground = true
}
}
}
Upvotes: 18
Reputation: 8176
In iOS 16, we got a native way to do this via scrollcontentbackground modifier.
You can either change the color by setting a color (ShapeStyle
) to scrollcontentbackground
.
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.scrollContentBackground(Color.pink)
Or you can hide the background .scrollContentBackground(.hidden)
and set a custom one with .backgroud
modifier.
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.background {
Image("ventura")
}
.scrollContentBackground(.hidden)
Upvotes: 4
Reputation: 297
For some reason color change is not working, you can try the .listStyle to .plain
Code:
struct ContentView: View {
var body: some View {
VStack {
Text("Test")
List {
ForEach(1 ..< 4) { items in
Text(String(items))
}
}
.listStyle(.plain)
}
}
Upvotes: 3
Reputation: 3805
The answer by Islom Alimov https://stackoverflow.com/a/59970379/9439097 seems to be the best implementation so far in my opinion.
Only drawback: this also changes the background color of all other list views in your app, so you need to manually change them back unless you want the same color everywhere.
Here is an example view:
import SwiftUI
struct TestView1: View {
init(){
UITableView.appearance().backgroundColor = UIColor(Color.clear)
}
@State var data = ["abc", "def"]
var body: some View {
VStack {
List {
ForEach(data, id: \.self) {element in
Text("\(String(describing: element))")
}
.background(Color.green)
.listRowBackground(Color.blue)
}
.background(Color.yellow)
Spacer()
Color.red
}
}
}
struct TestView1_Previews: PreviewProvider {
static var previews: some View {
TestView1()
}
}
produces:
Upvotes: 7
Reputation: 7902
you can use introspect
library from Github to set the background color for the underlying table view like this:
List { ... } .introspectTableView { tableView in
tableView.backgroundColor = .yellow
}
Upvotes: 0
Reputation: 127
Using UITableView.appearance().backgroundColor is not a good idea as it changes the backgroundColor of all tables. I found a working solution for color changing at the exact table you selected in iOS 14, 15.
We will change the color using a modifier that needs to be applied inside the List
extension View {
func backgroundTableModifier(_ color: UIColor? = nil) -> some View {
self.modifier(BackgroundTableModifier(color: color))
}
}
Our task is to find the UITableView and after that change the color.
private struct BackgroundTableModifier: ViewModifier {
private let color: UIColor?
@State private var tableView: UITableView?
init(color: UIColor?) {
self.color = color
}
public func body(content: Content) -> some View {
if tableView?.backgroundColor != color {
content
.overlay(BackgroundTableViewRepresentable(tableBlock: { tableView in
tableView.backgroundColor = color
self.tableView = tableView
}))
} else {
content
}
}
}
private struct BackgroundTableViewRepresentable: UIViewRepresentable {
var tableBlock: (UITableView) -> ()
func makeUIView(context: Context) -> BackgroundTableView {
let view = BackgroundTableView(tableBlock: tableBlock)
return view
}
func updateUIView(_ uiView: BackgroundTableView, context: Context) {}
}
class BackgroundTableView: UIView {
var tableBlock: (UITableView) -> ()
init(tableBlock: @escaping (UITableView) -> ()) {
self.tableBlock = tableBlock
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let tableView = findTableView(in: self) {
tableBlock(tableView)
}
}
private func findTableView(in view: UIView) -> UITableView? {
if let tableView = view as? UITableView {
return tableView
}
if let superView = view.superview {
return findTableView(in: superView)
}
return nil
}
}
In order to find UITableView, the modifier must be inside the List. Naturally, you need to ensure that the modifier is called only once, you do not need to apply it to each row. Here is an example of usage
List {
rows()
.backgroundTableModifier(.clear)
}
func rows() -> some View {
ForEach(0..<10, id: \.self) { index in
Row()
}
}
Upvotes: 1
Reputation: 727
Make extension List like:
extension List{
@available(iOS 14, *)
func backgroundList(_ color: Color = .clear) -> some View{
UITableView.appearance().backgroundColor = UIColor(color)
return self
}
}
Upvotes: 0
Reputation: 39
Simply Add UITableView
appearance background color in init()
method and add list style (.listStyle(SidebarListStyle())
. Don't forget to import UIKit
module
struct HomeScreen: View {
init() {
UITableView.appearance().backgroundColor = .clear
}
let tempData:[TempData] = [TempData( name: "abc"),
TempData( name: "abc"),
TempData( name: "abc"),
TempData( name: "abc")]
var body: some View {
ZStack {
Image("loginBackgound")
.resizable()
.scaledToFill()
List{
ForEach(tempData){ data in
Text(data.name)
}
}
.listStyle(SidebarListStyle())
}
.ignoresSafeArea(edges: .all)
}
}
Upvotes: 2
Reputation: 1352
If you want to avoid setting the appearance for all table views globally, you can combine UITableView.appearance(whenContainedInInstancesOf:)
with UIHostingController
. Thanks DanSkeel for the comment you left above pointing this out. This is how I used it:
public class ClearTableViewHostingController<Content>: UIHostingController<Content> where Content: View {
public override func viewDidLoad() {
UITableView.appearance(whenContainedInInstancesOf: [ClearTableViewHostingController<Content>.self]).backgroundColor = .clear
}
}
You can use ClearTableViewHostingController
like this:
let view = MyListView()
let viewController = ClearTableViewHostingController(coder: coder, rootView: view)
Then in your view you can set the list background color like so:
List {
Text("Hello World")
}
.background(Color.gray)
Upvotes: 0
Reputation: 1691
As an option you can .colorMultiply(Color.yourColor)
modifier.
Warning: this does not change the color! This only applies the Multiply
modifier to the current color. Please read the question before any action, because you are probably looking for: "How to CHANGE the background color of a List in SwiftUI" and this will not work for you. ❄️
Example:
List (elements, id:\.self ) { element in
Text(element)
}
.colorMultiply(Color.red) <--------- replace with your color
Upvotes: 3
Reputation: 527
Changing Background Color
As other have mentioned, changing the UITableView background will affect all other lists in your app.
However if you want different background colors you can set the default to clear, and set the background color in swiftui views like so:
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
// Ignore safe area to take up whole screen
.background(Color.purple.ignoresSafeArea())
.onAppear {
// Set the default to clear
UITableView.appearance().backgroundColor = .clear
}
You probably want to set the tableview appearance earlier, such as in the SceneDelegate or root view like so:
// SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else {
print("Returning because screne does not exist")
return
}
// Set here
UITableView.appearance().backgroundColor = .clear
let contentView = ContentView()
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
// Root App View
@main
struct ListBackgroundApp: App {
init() {
UITableView.appearance().backgroundColor = .clear
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Upvotes: 25
Reputation: 71
struct Details: View {
var body: some View {
Spacer().overlay(
List {
Text("Hello World!").font(.title2)
.listRowBackground(Color.clear)
Text("Hello World again").font(.title2)
.listRowBackground(Color.clear)
}.onAppear() {
UITableView.appearance().backgroundColor = UIColor.green
UITableViewCell.appearance().backgroundColor = UIColor.green
}
)
}
}
Upvotes: 7
Reputation: 1
Xcode Version 12.4
The Background property worked for me, but with the mandatory use of Opacity. Without opacity it is not work.
List {
ForEach(data, id: \.id) { (item) in
ListRow(item)
.environmentObject(self.data)
}
}
.background(Color.black)
.opacity(0.5)
Upvotes: -6
Reputation: 588
List is not perfect yet.
An option would be to use it like this -> List { ForEach(elements) { }}
instead of List($elements)
On my end this is what worked best up to now.
Like @FontFamily said, it shouldn't break any List
default behaviors like swiping.
Upvotes: 2
Reputation: 9
If anyone came here looking for solutions for background in landscape not full width on iPhone X/11 try:
.listRowBackground(Color("backgroundColour").edgesIgnoringSafeArea(.all))
Upvotes: 0
Reputation: 171
There is an argument: listRowBackground() in SwiftUI, but if you use List directly to iterate the data collection, it doesn't work.
Here is my workaround:
List {
// To make the background transparent, we have we use a ForEach as a wrapper
ForEach(files) {file in
Label(
title: { Text(file.name ?? fileOptionalFiller).lineLimit(listRowTextLineLimit) },
icon: { AppIcon.doc.foregroundColor(.primary) }
)
}
.listRowBackground(Color.primary.colorInvert())
}
Basically, listRowBackground() works if you use a ForEach inside List.
Upvotes: 17
Reputation: 351
I do not know what is the connection but if you wrap the list with Form
it is working.
Form {
List(viewModel.currencyList, id: \.self) { currency in
ItemView(item: currency)
}
.listRowBackground(Color("Primary"))
.background(Color("Primary"))
}
Upvotes: 11
Reputation: 769
This will set the background of the whole list to green:
init() {
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().backgroundColor = .green
UITableView.appearance().backgroundColor = .green
}
Upvotes: 54
Reputation: 472
For me, a perfect solution to change the background of List in SwiftUI is:
struct SomeView: View {
init(){
UITableView.appearance().backgroundColor = UIColor(named: "backgroundLight")
}
...
}
Upvotes: 2
Reputation: 11752
I've inspired some of the configurator used to config per page NavigationView nav bar style and write some simple UITableView per page configurator not use UITableView.appearance() global approach
import SwiftUI
struct TableViewConfigurator: UIViewControllerRepresentable {
var configure: (UITableView) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<TableViewConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TableViewConfigurator>) {
let tableViews = uiViewController.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()
for tableView in tableViews {
self.configure(tableView)
}
}
}
Then there is UIView extension needed to find all UITableViews
extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type) -> [T] {
var result = self.subviews.compactMap {$0 as? T}
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}
And usage at the end is:
List {
}.background(TableViewConfigurator {
$0.backgroundColor = .red
})
Maybe one thing should be improved that is usage of navigationController?.topViewController to make it work even without navigationController in view controllers hierarchy
Upvotes: 0
Reputation: 3154
struct ContentView: View {
var strings = ["a", "b"]
var body: some View {
List {
ForEach(strings, id: \.self) { string in
Text(string)
}.listRowBackground(Color.green)
}
}
}
Upvotes: 51
Reputation: 745
You can do it by changing UITableView
's appearance.
UITableView.appearance().backgroundColor = UIColor.clear
just put this line in Appdelegate
's didFinishLaunchingWithOptions
method.
In replace of UIColor.clear
set whatever color you want to add in background color of list
.
Upvotes: 33