How to get a row count in SwiftUI List - list

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)")) {

Related

SwiftUI - Change List row background colour programatically

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)
}

Deleting SwiftUI Items from List Based on Section

I have a TodoListView which displays a List in sections. The sections can be pending or completed as shown below. I am using Realm for my app. The problem is that how can I find out the task I want to delete, since they can belong in different sections.
What should I do in my onDelete function to delete the task in a particular section.
enum Sections: String, CaseIterable {
case pending = "Pending"
case completed = "Completed"
}
struct TodoListView: View {
#ObservedResults(Task.self) var tasks: Results<Task>
var pendingTasks: [Task] {
tasks.filter { $0.isCompleted == false }
}
var completedTasks: [Task] {
tasks.filter { $0.isCompleted == true }
}
var body: some View {
let _ = print(Self._printChanges())
List {
ForEach(Sections.allCases, id: \.self) { section in
Section {
let filteredTasks = section == .pending ? pendingTasks: completedTasks
if filteredTasks.isEmpty {
Text("No tasks")
}
ForEach(filteredTasks, id: \._id) { task in
HStack {
TaskCellView(task: task)
}
}.onDelete { indexSet in
// What to do here:
// can I get the element which I swiped to delete on
}
} header: {
Text(section.rawValue)
}
}
}.listStyle(.plain)
}
}
UPDATE:
I was able to write the following code and it seems to be working fine:
.onDelete { indexSet in
var filteredTasks: [Task] = []
switch section {
case .pending:
filteredTasks = tasks.filter { $0.isCompleted == false }
case .completed:
filteredTasks = tasks.filter { $0.isCompleted == true }
}
indexSet.forEach { index in
let task = filteredTasks[index]
$tasks.remove(task)
}
}

asynchronous initialisation with swiftui

Basically - I run up against this a lot - I don't understand how you correctly do asynchronous initialisation in swift with callbacks. (with combine - I can do it). In particular - I have this code:
struct MyView : View {
#State var initialised : Bool = false
init()
{
var initialisedBinding = $initialised
Photos.PHPhotoLibrary.RequestAuthorization {
status in
if (status == Photos.PHAuthorizationStatus.authorized) {
print("here I am")
initialisedBinding.wrappedValue = true
initialisedBinding.update()
}
}
}
var body : some View {
VStack {
if (initialised) {
Text("yep")
} else {
Text("nope")
}
}
}
And when I run it - I get the print out - but the text never changes - it always remains "nope". What am I doing wrong, and how do I do it right? (Without using combine - I can do it with like a currentValueSubject and a .onreceive - but it's extra overhead, and I really want to know why the above code doesn't work - obviously I'm understanding something bad)
State is not ready in init yet, so you bound to nowhere. Moreover such activity in init is not good, because view can be created many times during rendering. The more appropriate place is .onAppear
struct MyView : View {
#State var initialised : Bool = false
var body : some View {
VStack {
if (initialised) {
Text("yep")
} else {
Text("nope")
}
}.onAppear {
Photos.PHPhotoLibrary.RequestAuthorization {
status in
if (status == Photos.PHAuthorizationStatus.authorized) {
print("here I am")
self.initialised = true
}
}
}
}
}

put observedObject in List

I get the data from my api and create a class for them. I can use swifyJSON to init them correctly. The problem is that when I put my observedObject in a List, it can only show correctly once. It will crashed after I changed the view. It's very strong because my other List with similar data struct can work.(this view is in a tabView) Is somebody know where my getAllNotification() should put view.onAppear() or List.onAppear()? Thanks!!
class ManagerNotification : Identifiable, ObservableObject{
#Published var id = UUID()
var notifyId : Int = 0
var requestId : Int = 0
var requestName: String = ""
var groupName : String = ""
// var imageName: String { return name }
init(jsonData:JSON) {
notifyId = jsonData["notifyId"].intValue
requestId = jsonData["requestId"].intValue
requestName = jsonData["requestName"].stringValue
groupName = jsonData["groupName"].stringValue
}
}
import SwiftUI
import SwiftyJSON
struct NotificationView: View {
var roles = ["userNotification", "managerNotification"]
#EnvironmentObject var userToken:UserToken
#State var show = false
#State private var selectedIndex = 0
#State var userNotifications : [UserNotification] = [UserNotification]()
#State var managerNotifications : [ManagerNotification] = [ManagerNotification]()
var body: some View {
VStack {
Picker(selection: $selectedIndex, label: Text(" ")) {
ForEach(0..<roles.count) { (index) in
Text(self.roles[index])
}
}
.pickerStyle(SegmentedPickerStyle())
containedView()
Spacer()
}
.onAppear(perform: getAllNotification)
}
func containedView() -> AnyView {
switch selectedIndex {
case 0:
return AnyView(
List(userNotifications) { userNotification in
UserNotificationCellView(userNotification: userNotification)
}
)
case 1:
return AnyView(
List(managerNotifications) { managernotification in
ManagerNotificationCellView(managerNotification : managernotification)
}
.onAppear(perform: getManagerNotification)
)
default:
return AnyView(Text("22").padding(40))
}
}
func getAllNotification(){
// if (self.userNotifications.count != 0){
// self.userNotifications.removeAll()
// }
// I think the crash was in here, because when i don't use removeAll().
// It works fine, but i don't want every times i change to this view. my array will be longer and
// longer
if (self.managerNotifications.count != 0){
self.managerNotifications.removeAll()
}
NetWorkController.sharedInstance.connectApiByPost(api: "/User/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.userNotifications.append(UserNotification(jsonData: notification))
}
}
}
}
}
NetWorkController.sharedInstance.connectApiByPost(api: "/Manager/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.managerNotifications.append(ManagerNotification(jsonData: notification))
}
}
}
}
}
}
func getManagerNotification(){
// if (self.managerNotifications.count != 0){
// self.managerNotifications.removeAll()
// }
print(self.managerNotifications.count)
NetWorkController.sharedInstance.connectApiByPost(api: "/Manager/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.managerNotifications.append(ManagerNotification(jsonData: notification))
}
}
}
}
}
}
}
error message
Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. reason: 'attempt to delete section 0, but there are only 0 sections before the update'
I think you are confused about the role of #State and #ObservebableObject; it's not like MVC where you replace the ViewController with a SwiftUI.View as it appears you are trying to do in your example. Instead the view should be a function of either some local #State and/or an external #ObservedObject. This is closer to MVVM where your #ObservedObject is analogous to the ViewModel and the view will rebuild itself in response to changes in the #Published properties on the ObservableObject.
TLDR: move your fetching logic to an ObservableObject and use #Published to allow the view to subscribe to the results. I have an example here: https://github.com/joshuajhomann/TVMaze-SwiftUI-Navigation

Issue with ForEach in SwiftUI

I'm trying to create a list from an array of Request objects.
I have defined a custom view RequestRow to display a Request.
The following works to display a list of requests…
struct RequestsView : View {
let requests: [Request]
var body: some View {
List {
ForEach(0..<requests.count) { i in
RequestRow(request: self.requests[i])
}
}
}
}
but the following won't compile…
struct RequestsView : View {
let requests: [Request]
var body: some View {
List {
ForEach(requests) { request in
RequestRow(request: request)
}
}
}
}
Cannot convert value of type '(Request) -> RequestRow' to expected argument type '(_) -> _'
Any thoughts as to what the issue might be?
OK, I soon figured out the answer. Per the Apple's docs, the array elements must be Identifiable, so this works…
var body: some View {
List {
ForEach(requests.identified(by: \.self)) { request in
RequestRow(request: request)
}
}
}
I'm sure I won't be the last person to have this problem, so I'll leave this here for future reference.
I have the same problem, but in my case Component is a Protocol, so it can't be conformed to Identifiable
VStack {
ForEach(components.identified(by: \.uuid)) { value -> UIFactory in
UIFactory(component: value)
}
}
However, If I try something like this it works fine
VStack {
ForEach(components.identified(by: \.uuid)) { value -> UIFactory in
Text(value.uuid)
}
}