I'm trying to change the background colour of a row in a list based on a value
I have an Int16 value stored in card.owned and I want to change the background if this value is above 0, otherwise use the normal background.
Previously with a table view I could change it with the cellForRowAtIndexPath method, but not sure how to do it with SwiftUI without changing every row
Currently I have
List {
ForEach(cards) { section in
let header: String = nameForSectionHeader(sectionID: section.id)
Section(header: Text(header)) {
ForEach(section) { card in
NavigationLink(destination: CardView(card: card)) {
VStack(alignment: .leading) {
if let name = card.name, let id = card.cardID {
Text("\(id) - \(name)")
}
if let set = card.set, let setName = set.name {
Text("Set: \(setName)")
}
if card.owned > 0 {
Text("Owned: \(card.owned)")
}
}
}
}
}
}
.listRowBackground(lightGreen)
}
Related
Im going through SwiftUI Controls in detail and writing example code for each init.
Theres two sets of inits for Picker.
One where you fill your list from an array etc. and what you pick goes into selection: binding.
But whats the second set of inits for?
Theyre under section 'Creating a picker for a collection'
I can fill the array from a collection using both these inits ok
But how do you get the selected item using the 2nd set of inits?
The selection: param is no longer a binding to an ivar but a Keypath to fill the list.
My question is how do I get the selected item using the 2nd set of inits.
See inits here:
https://developer.apple.com/documentation/swiftui/picker
For Picker there are 6 inits.
3 under 'Creating a picker'
these are ok. I fill the list from an array for example and store the selected item in a single result specified by the selection: param. It binds the result to one ivar.
There are also 3 inits under 'Creating a picker for a collection'
I got this to display the items from a collection
e.g. I modified the example code in the apple docs. The code in the docs doesn't compile so apple may be missing stuff.
import SwiftUI
enum Thickness: String, CaseIterable, Identifiable {
case thin
case regular
case thick
var id: String { rawValue }
}
//to use in ist must be Hashable
struct Border: Identifiable {
var color: Color
var thickness: Thickness
//Identifiable > Hashable > id > String
//var id: String { return "\(color.hashValue)" }
let id = UUID()
}
extension Color{
func colorName() -> String{
if self == Color.black{
return "black"
}
else if self == Color.red{
return "red"
}
else{
return "UNHANDLED"
}
}
}
struct CLCPickers_selection_FromCollection_View: View {
#State private var selectedObjectBorders = [
Border(color: .black, thickness: .thin),
Border(color: .red, thickness: .thick)
]
var body: some View {
VStack{
//------------------------------------------------------------------
Picker(
"Border Thickness",
sources: $selectedObjectBorders,
selection: \.thickness
) {
//------------------------------------------------------------------
//I added
//id: \.self
//Picker: the selection "thin" is invalid and does not have an associated tag, this will give undefined results.
//------------------------------------------------------------------
ForEach(Thickness.allCases,
id: \.self)
{ thickness in
Text(thickness.rawValue)
}
}
//------------------------------------------------------------------
Divider()
//------------------------------------------------------------------
//This just lists the colors in the arrays of Border
//QUESTION - how do I find out the currenly selected one?
//normaly selection: in the picker would be bound to the picked item
//but for this init selection: is a keypath
//selection: \.thickness
//so I can fill the Picker list using the keypath into the Border array.
//BUT HOW DO I FIND OUT THE CURRENTLY SELECTED ITEM?
//theres no binding?
//is there a .selectedItem property some where?
List(selectedObjectBorders) {
Text("\($0.color.colorName())")
}
}
}
}
Question was answered but poster removed it for some reason.
answer: this picker init which set the thinkness ivar of EVERY Border object in the collection.
To see the change I should have displayed the result to show thickness.rawvalue to see the change in every Border object
List(selectedObjectBorders) { border in
HStack{
Text("\(border.color.colorName())")
Text("\(border.thickness.rawValue)") //<<- will change when you select an item. All will match.
}
}
I am trying to display the number of rows in a section in its header as shown below as COUNTHERE. The issue I'm running into is that I can't put any code inside the if statement that is not a view so I can't compute anything. Ideas?
struct Day1View: View {
var displayEmployees: [Employee]
var body: some View {
List {
Section(header: Text("Early (\(COUNTHERE)")) {
ForEach(displayEmployees) { employee in
if employee.shift == .early {
switch employee.post {
case .kitchen : Text(employee.name).foregroundColor(.blue)
case .floor : Text(employee.name).foregroundColor(.yellow)
case .upstairs : Text(employee.name).foregroundColor(.red)
case .greeting : Text(employee.name).foregroundColor(.green)
default : Text(employee.name).foregroundColor(.gray)
}
}
}
}
}
Since the section you showed is only for .early shift employees, you can get the count using a filtered version of the original array:
displayEmployees.filter({$0.shift == .early}).count
So your section becomes:
Section(header: Text("Early (\(displayEmployees.filter({$0.shift == .early}).count)")) {
Or, you can add a new computed property for the count:
var displayCount: Int {
return displayEmployees.filter({$0.shift == .early}).count
}
...
Section(header: Text("Early (\(displayCount)")) {
I am trying to get NSFontPanel/NSFontManager to work in a SwiftUI Document Template app. I have the following which is a customize version of one I found on GitHub. This lets me pick the size, face, style, etc.
Interestingly, a color picker is included in the FontPanel. The documentation doesn't seem to say this. Is this something new?
Anyway, I would like to either be able to use the color picker to let the user select a color, or if not I would like to hide the color picker - at is not "critical" to this application. I am using this to allow customization of text in a sidebar, so color is nice, but not necessary. Currently the Font settings are working, but the color selection displays, and let you pick on, but it always returns System Color.
Any help would be appreciated.
NOTE: I didn't include the FontPickerDelegate, it just calls this:
public struct FontPicker: View{
let labelString: String
#Binding var font: NSFont
#State var fontPickerDelegate: FontPickerDelegate?
public init(_ label: String, selection: Binding<NSFont>) {
self.labelString = label
self._font = selection
}
let fontManager = NSFontManager.shared
let fontPanel = NSFontPanel.shared
#AppStorage("setSidebarFont") var setSidebarFont = "System"
#AppStorage("setSidebarFontSize") var setSidebarFontSize = 24
#AppStorage("setSidebarFontColor") var setSidebarFontColor = "gray"
public var body: some View {
HStack {
Text(labelString)
Button {
if fontPanel.isVisible {
fontPanel.orderOut(nil)
return
}
self.fontPickerDelegate = FontPickerDelegate(self)
fontManager.target = self.fontPickerDelegate
fontManager.action = #selector(fontPickerDelegate?.changeAttributes)
fontPanel.setPanelFont(self.font, isMultiple: false)
fontPanel.orderBack(nil)
} label: {
Text("Font Selection: \(setSidebarFont)")
.font(.custom(setSidebarFont, size: CGFloat(setSidebarFontSize)))
}
}
}
func fontSelected() {
self.font = fontPanel.convert(self.font)
setSidebarFont = self.font.displayName ?? "System"
setSidebarFontSize = Int(self.font.pointSize)
var newAttributes = fontManager.convertAttributes([String : AnyObject]())
newAttributes["NSForegroundColorAttributeName"] = newAttributes["NSColor"]
newAttributes["NSUnderlineStyleAttributeName"] = newAttributes["NSUnderline"]
newAttributes["NSStrikethroughStyleAttributeName"] = newAttributes["NSStrikethrough"]
newAttributes["NSUnderlineColorAttributeName"] = newAttributes["NSUnderlineColor"]
newAttributes["NSStrikethroughColorAttributeName"] = newAttributes["NSStrikethroughColor"]
print("\(newAttributes["NSForegroundColorAttributeName"]!)")
}
}
I have an app with a number of categories--some the can be changed by the user and some that can't. The categories that can't be changed are stored currently in an array while categories that the user can change are stored in a class. The problem comes when creating expense entries where the picker needs to show both types of categories.
The other side of the coin is to place all the categories (text strings) in the class. Here the expense entry picker and class storage will work ok, but then there is the problem of preventing the user from deleting the default categories.
I'm guessing that latter option is the better route since it will place all the categories in the picker list. Here is the code for storing the dynamic categories. I suppose I could add some init() code to store the categories that don't change. Not sure exactly how to do that.
struct CatItem: Codable {
var catName: String
var catPix: String
}
class Categories: ObservableObject {
#Published var catItem: [CatItem] {
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(catItem) {
UserDefaults.standard.set(encoded, forKey: "workCat")
}
}
}
init() {
if let catItem = UserDefaults.standard.data(forKey: "workCat") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([CatItem].self, from: catItem) {
self.catItem = decoded
return
}
}
self.catItem = []
}
}
How would you prevent the user from deleting some of the fixed categories? Usually you have a list with an onDelete statement.
Can you use the index to determine if deleting is allowed? For example don't delete entry if indexSet[index] < 8?
.onDelete { indexSet in
for index in indexSet {
remove entry
}
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
}
I assume you wanted something like
.onDelete { indexSet in
guard let i = indexSet.first, indexSet[i] < 8 else { return }
// .. other code
I resolved this storage quandary by deciding to store both the permanent and changeable categories in the same class catItem. In the module where the categories may be viewed and where new categories may be added I use a filtered list showing only the categories that may be edited.
This is how the categories are initialized at startup:
let item = CatItem(catName: "name1", catPix: "sf symbol", noShow: true)
self.catItem.append(item)
This is how the categories are displayed in a list:
List {
ForEach(categories.catItem, id: \.catName) { item in
if item.noShow == false {
HStack {
Text(item.catName)
.padding(.horizontal, 10)
Spacer()
Image(systemName: item.catPix).resizable()
.frame(width: 30, height: 30)
}
}
}
.onDelete(perform: removeItems)
}
So if the user can't see the permanent categories in List, then they can't delete them. The user can only delete the categories that they add.
When the user adds new categories the noShow parameter is set to false.
In the picker the categories are not filtered so all categories may be viewed.
Im trying to create a list of Hstack'd cards, That is to say, I want to create a scroll view of a series of rows. Each row would contain an HStack of two views displayed side by side, and initialized by some list data structure.
struct MyHStackView: View {
var myArray = [SomeStruct(1), SomeStruct(3), SomeStruct(4), SomeStruct(5), SomeStruct(6)]
var body: some View {
ScrollView(.vertical) {
VStack {
ForEach(0..<self.myArray.count) { index in
HStack {
SubView(myArray[index])
SubView(myArray[index+1])
}
}
}
}
The only issue is my current implementation touches every element of the array, is there a stride function built into the new ForEach so that I can index on every other element on the array to initialize the row? How would you approach this?
If just every other, you may try
VStack {
ForEach(0 ..< self.myArray.count/2) { index in
HStack {
SubView(myArray[index * 2])
SubView(myArray[index * 2 + 1])
}
}
}
Otherwise, you may need to use the stride function:
ForEach(Array(stride(from: 0, to: self.myArray.count, by: 2)), id: \.self) { index in
// ...
}
For simple use-cases the solution posted by E. Coms may work. If you plan on modifying or re-ordering the list, it may give you trouble since the id specified is the same as the index and List won't be able to correctly animate removals/additions/re-ordering.
I would create a data-structure to represent the tuple in an identifiable manner:
struct SomeStructTouple: Identifiable {
let a: SomeStruct
let b: SomeStruct
var id: String {
"\(a.id)-\(b.id)"
}
}
and then create an array of touples to generate the list.