SwiftUI, I have a List that on occasion dosnt have enough items to fill it all, as a result im seeing the white 'background' at the bottom.
List {
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
}
.cornerRadius(15)
.border(Color.black, width: 2)
Ive tired applying the background item, but made no difference i.e.
List {
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
}
.background(Color.blue)
.cornerRadius(15)
.border(Color.black, width: 2)
Anyone any ideas?
Please try using:
ScrollView {
VStack {
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
ListItemCell(item: "xxx")
}
}
.background(Color.blue)
.cornerRadius(15)
.border(Color.black, width: 2)
Related
I'm working on a SwiftUI project and I'm having trouble with a list having a different background color than the rest of my screen. Specifically, the background of the list placeholder (i.e. before the list gets loaded in). Here is what I have so far:
var body: some View {
NavigationStack {
ZStack {
gradient
.opacity(0.35)
.ignoresSafeArea()
VStack {
Text("Search For Breweries")
.font(.system(.title3, design: .rounded))
.fontWeight(.bold)
TextField("Search by Name", text: $brewSearch)
.frame(width: 300, height: 50.0)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.border(.primary)
.cornerRadius(3.0)
.padding()
.onChange(of: brewSearch) { newValue in
brewSearch = newValue
Task.init(operation: {
if !newValue.isEmpty {
self.networkManager.fetchDataBySearch(name: newValue.replacingOccurrences(of: " ", with: "_"))
}
})
}
HStack(alignment: .center) {
ZStack {
Capsule()
.fill(Color("Brown"))
Capsule()
.fill(.black.opacity(0.15))
.padding(8)
HStack {
Text("Search by your location")
Image(systemName: "location")
.font(.system(size: 24, weight: .bold))
}
} //: Button ZStack
.foregroundColor(.white)
.frame(width: 300, height: 80)
.onTapGesture {
locationManager.requestAuthorization(always: true)
guard let latitude = CLLocationManager().location?.coordinate.latitude else {return}
guard let longitude = CLLocationManager().location?.coordinate.longitude else {return}
networkManager.fetchDataByLocation(latitude: latitude, longitude: longitude)
}
} //: HStack
List {
ForEach(networkManager.breweries) { brewery in
// All the list items that will get loaded in
}
}
.background(.clear)
.scrollContentBackground(.hidden)
}
}
}
}
Once the list gets loaded in, the background becomes the same as the rest of the screen, but before anything loads in, the section where the list will appear is white. I'll attach some screenshots of what I'm talking about:
[
[
How can I change it so the white area where the list will go is the same color of the rest of the screen? Right now I don't care about each list item's background color, just the background for where the list will go.
you could try this approach:
List {
ForEach(networkManager.breweries) { brewery in
// All the list items that will get loaded in
}
}.blendMode(networkManager.breweries.isEmpty ? .destinationOver : .normal)
I want to limit the width of a Text to a given number. I thought that using frame(maxWidth:) would do the job, however it does not behave as expected at all. It seems that setting a max width makes the text grow to this size, whatever the size it should be.
Here's a code snippet:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.frame(maxWidth: 200)
}
.background(Color.yellow)
.border(.black, width: 1)
VStack {
Text("A longer text that should wrap to the next line.")
.frame(maxWidth: 200)
}
.background(Color.yellow)
.border(.black, width: 1)
}
Expected result:
Actual result:
Generally, Text automatically grow/shrink by itself. To solve your problem, just set maxWidth to your VStack instead of Text to control your maxWidth, then add backgrounds modifiers to your text instead.
Code is below the image:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
}
.frame(maxWidth: 200)
VStack {
Text("A longer text that should wrap to the next line.")
.background(Color.yellow)
.border(.black, width: 1)
}
.frame(maxWidth: 200)
}
The VStack adjusts its width to the max width of its elements. This is the native behavior of the VStack.
However, if you do the following you'll get the result you want:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
}.frame(maxWidth: 200)
VStack {
Text("A longer text that should wrap to the next line.")
.frame(maxWidth: 200)
}.background(Color.yellow)
.border(.black, width: 1)
}
Assuming that we need a generic component which should work similarly with specified behavior independently of provided string, like
VStack(spacing: 20) {
TextBox("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
TextBox("A longer text that should wrap to the next line.")
.background(Color.yellow)
.border(.black, width: 1)
}
then some run-time calculations required.
Here is a demo of possible solution (with real edge markers). Tested with Xcode 13.4 / iOS 15.5
Main part:
struct TextBox: View {
private let string: LocalizedStringKey
private let maxWidth: CGFloat
#State private var width: CGFloat
init(_ text: LocalizedStringKey, maxWidth: CGFloat = 200) { // << any default value
// ...
}
// ...
Text(string)
.background(GeometryReader {
Color.clear
.preference(key: ViewSideLengthKey.self, value: $0.frame(in: .local).size.width)
})
.onPreferenceChange(ViewSideLengthKey.self) {
self.width = min($0, maxWidth)
}
.frame(maxWidth: width)
Test code on GitHub
I want to change the background color of my list when it is in .editMode. What I tried so far is the general approach to changing the .background of a list via the initialisation of the view the following way:
init() {
UITableView.appearance().backgroundColor = .clear
UITableView.appearance().separatorColor = .none
UITableView.appearance().separatorStyle = .none
}
and the setting the background color of the list with the .background modifier.
This is working, but once in .editMode the Color(.systemsBackground) (black) is shown instead of the my custom Color (
Picture of List in editMode).
My code for the list is:
List {
ForEach(categoryVM.selectedCategories) { category in
NavigationLink(destination: EditCategoryColorView(category: category, editType: .edit, name: category.name, color: category.color)){
ZStack {
HStack {
Text("\(category.name)")
Spacer()
Color("\(category.color)")
.aspectRatio(1, contentMode: .fit)
.cornerRadius(6)
.frame(width: 40, height: 40)
}
}
}.padding(3)
}.onDelete(perform: categoryVM.deleteCategory)
.onMove(perform: move)
.foregroundColor(colorScheme == .dark ? Color.white : Color.black)
.background(colorScheme == .dark ? Color("DarkGrayApp") : Color.white)
.listRow()
}
.padding(.vertical)
// to enable edit mode
EditButton()
In the code .listRow() is an extension, so the rows fill the whole space available.
extension View {
func listRow() -> some View {
self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1))
.background(Color(.systemBackground))
}
}
I tried changing the color in the extension as well but it did not work.
Any idea how I could change the background color once in .editMode as well?
Thank you!
Just an update: .listRowBackground(Color.clear) did the trick for me.
I am not gonna lie, but the modifiers in SwiftUI are super tricky. I want to get rid of the grey area above my list. I have tried all, padding, offset, frame, you name it. It's still there. Why is it there anyway. Is it some kind a header that I could possibly undefine? Anyway, here is the code:
List{
ForEach(prim, id: \.id) { value in
GeometryReader { geometry in
HStack {
Text(value.label)
.font(.system(size: 12))
.padding(.vertical, 0)
.frame(width: geometry.size.width/4)
Spacer(minLength: geometry.size.width/4)
TextField("", value: value.data , formatter: NumberFormatter())
.font(.system(size: 12))
.padding(.vertical, 0)
.keyboardType(.numberPad)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding(.top, 0)
}.padding(.top, 0)
}.padding(.top, 0)
}
.padding(.top, 0)
.listStyle(GroupedListStyle())
I have not defined the grey area, so why is it there? It pushes my list down and so my third item in the list disappears.
If it is not empty NavigationView (because it is not clear where is this List placed), then it is due to used list style.
You can use instead plain style
}
.padding(.top, 0)
.listStyle(PlainListStyle())
Before var body: some View put this:
init()
{
UITableView.appearance().backgroundColor = .clear
}
I don't understand why I have extraneous vertical spacing between elements in a ForEach that is inside a VStack that is inside a ScrollView when using a GeometryReader to render a custom horizontal separator line.
ScrollView {
ForEach(self.model1.elements, id: \.self) { element in
VStack.init(alignment: .leading, spacing: 0) {
// Text("test") // image 3: works correctly
// .background(Color.blue)
GeometryReader { geometry in
Path { path in
path.move(to: .init(x: 0, y: 0))
path.addLine(to: .init(x: geometry.size.width, y: 0))
}
.strokedPath(.init(lineWidth: 1, dash: [1,2]))
}
// .frame(maxHeight: 1) // image 2: uncommenting this line doesn't fix the spacing
.foregroundColor(.red)
.background(Color.blue)
HStack {
Text("\(element.index+1)")
.font(.system(.caption, design: .rounded))
.frame(width: 32, alignment: .trailing)
Text(element.element)
.font(.system(.caption, design: .monospaced))
.frame(maxWidth:nil)
Spacer()
}
.frame(maxWidth:nil)
.background(Color.green)
}
.border(Color.red)
}
}
The above code produces this:
With .frame(maxHeight: 1) the blue padding is gone, but still there is white space between the consecutive HStacks.
I want the vertical spacing to be like in this image, ie 0. This image is achieved by uncommenting the Text("test") source, and commenting the GeometryReader code.
I'm using Xcode 11.3.1 (11C504)
If I understand your ultimate goal, it's to have each row bordered above by a dotted line, with no padding between them, like this:
In that case, IMO you should put the lines in a background. For example:
ScrollView {
VStack(alignment: .leading) {
ForEach(self.elements, id: \.self) { element in
HStack {
Text("\(element.index+1)")
.font(.system(.caption, design: .rounded))
.frame(width: 32, alignment: .trailing)
Text(element.element)
.font(.system(.caption, design: .monospaced))
Spacer()
}
.background(
ZStack(alignment: .top) {
Color.green
GeometryReader { geometry in
Path { path in
path.move(to: .zero)
path.addLine(to: CGPoint(x: geometry.size.width, y: 0))
}
.strokedPath(StrokeStyle(lineWidth: 1, dash: [1,2]))
.foregroundColor(Color.red)
}
}
)
}
}
}
A key point is that ScrollView is not a vertical stacking container. It just takes its content and makes it scrollable. Its content in your code is a ForEach, which generates views; it doesn't really intend to lay them out. So when you combine a ScrollView with a ForEach, nothing is really in charge of placing the views the way you want. To stack views vertically, you want a VStack.
You can apply this to your structure just as well, but adding another VStack, and get the same results:
ScrollView {
VStack(spacing: 0) { // <---
ForEach(self.elements, id: \.self) { element in
VStack(alignment: .leading, spacing: 0) {
GeometryReader { geometry in
Path { path in
path.move(to: .init(x: 0, y: 0))
path.addLine(to: .init(x: geometry.size.width, y: 0))
}
.strokedPath(.init(lineWidth: 1, dash: [1,2]))
}
.foregroundColor(.red)
.frame(height: 1)
HStack {
Text("\(element.index+1)")
.font(.system(.caption, design: .rounded))
.frame(width: 32, alignment: .trailing)
Text(element.element)
.font(.system(.caption, design: .monospaced))
.frame(maxWidth:nil)
Spacer()
}
}
.background(Color.green) // <-- Moved
}
}
}
The spacing is generated by VStack.
When creating a VStack, add the parameter for spacing and set it to 0.
VStack(spacing: 0) {
Element1()
Element2()
Element3()
Element4()
}
VStack
The same spacing property is available for HStack, LazyVStack and LazyHStack