I'm working on a SwiftUI project where I have a list of referrals displayed using List and ForEach. The list is part of the MyReferralsList view, and I want to optimize the UI rendering to avoid redrawing the entire view when the data property changes.
I have already tried implementing the Equatable protocol for all the relevant data types and also for the view itself. I also tried using the id parameter with the ForEach, but it didn't work as expected.
The ScrollingViewForBtns is fetching the buttons title from an API but i do not call that API from here. I am just changing the myReferralsListViewModel.data from this view. The data array is a published object and the data is only being used by the ReferralListSubView.
Here's what I've tried so far:
Conforming to Equatable protocol for all the relevant data types and view. Using the id parameter with ForEach to uniquely identify each element and also used id for the subview that contains the ForEach loop. Despite these efforts, every time I fetch new data and update the myReferralsListViewModel.data, the entire MyReferralsList view is redrawn, including the ScrollingViewForBtns and referralsListView.
Here's the simplified code for the MyReferralsList view:
struct MyReferralsList: View, Equatable {
@ObservedObject var myReferralsListViewModel: MyReferralsListVM
@State private var selectedStatus: Int = 0
@State private var dataChanged: Bool = false
private var organizationId: Int = 0
init(organizationId: Int, statusId: Int? = 0){
self.organizationId = organizationId
self.myReferralsListViewModel = MyReferralsListVM(referralsService:ReferralsService(networking: Networking.shared), organizationId: organizationId)
}
var body: some View {
ZStack(alignment: .top) {
Color.white
VStack(alignment: .leading, spacing: 16.0) {
/// Buttons to filter the referrals by stauts
Group{
ScrollingViewForBtns(selectedButtonIndex: $selectedStatus,statusBtnTapped: { id in
/**
- if selected index is 0 means "All" referrals are selected so we fetch all referrals passing only the organizationID
- Else we pass the id for the selected status and fetch the referrals for the selected Status
*/
if selectedStatus == 0 {
myReferralsListViewModel.fetchData(id: organizationId)
}else{
myReferralsListViewModel.fetchReferralForStatus(statusId: id)
}
})
}
ReferralListSubView(data: myReferralsListViewModel.data).id(dataChanged)
Spacer()
}
.padding(.vertical, 14)
.padding(.horizontal, 15)
}
.foregroundColor(Color("textGray"))
.onChange(of: myReferralsListViewModel.data) { _ in
dataChanged.toggle()
}
}
static func == (lhs: MyReferralsList, rhs: MyReferralsList) -> Bool {
print("Comparing MyReferralsList instances...")
print("lhs.selectedStatus: \(lhs.selectedStatus), rhs.selectedStatus: \(rhs.selectedStatus)")
print("lhs.organizationId: \(lhs.organizationId), rhs.organizationId: \(rhs.organizationId)")
// Compare data property
print("lhs.myReferralsListViewModel.data: \(lhs.myReferralsListViewModel.data)")
print("rhs.myReferralsListViewModel.data: \(rhs.myReferralsListViewModel.data)")
let dataEqual = lhs.myReferralsListViewModel.data == rhs.myReferralsListViewModel.data
print("Data is equal: \(dataEqual)")
return lhs.selectedStatus == rhs.selectedStatus &&
lhs.organizationId == rhs.organizationId &&
lhs.myReferralsListViewModel.data == rhs.myReferralsListViewModel.data
}
}
code for ReferralListSubView:
struct ReferralListSubView: View {
let data: [Referral]
var body: some View {
List{
ForEach(data, id:\.id) { referral in
NavigationLink(destination: ReferralDetailView(referral: referral)) {
ReferralsCells(referral: referral)
}
.frame(height: 81.0)
.padding(.horizontal)
.background(Color.white)
.cornerRadius(6)
.shadow(color: Color.black.opacity(0.2), radius: 2, x: 0, y: 2)
.listRowBackground(Color.clear)
}
}
.listRowSeparator(.hidden)
.listStyle(PlainListStyle()) // Set the list style to PlainListStyle()
.background(Color.white) //
}
}
code for ScrollingViewForBtns:
struct ScrollingViewForBtns: View {
@ObservedObject var referralsStatusVM = ReferralsStatusViewModel(referralService: ReferralsService(networking: Networking.shared))
@Binding var selectedButtonIndex: Int
var statusBtnTapped : (Int) -> Void
var body: some View {
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 16){
ForEach(referralsStatusVM.buttons.indices, id:\.self) { index in
StatusButton(btnText: referralsStatusVM.buttons[index].name, btnAction: {
selectedButtonIndex = index // This will help to change selected button color
statusBtnTapped(referralsStatusVM.buttons[index].id) // Passing the selected status id to the parent view
// this handles the API from parent view
}, isSelected: Binding(
get: { selectedButtonIndex == index },
set: { _ in }))
}
}
.padding(.vertical,10)
.padding(.horizontal,2)
}
}
}
I just need to update the ReferralListSubView when the data array changes.
Could someone please help me identify what I might be missing or suggest alternative approaches to optimize the rendering and prevent the entire view from being redrawn every time data changes?
Thank you in advance for any assistance!