SwiftUI: Remove duplicates from a json array to then display in a list view

Just starting out with SwiftUI and i’m building a small macOS app that would replicate an in-house app store. So far I have implemented an API call, Model, ViewModel and View, based on several online courses I have followed. This has enabled me to display a list of categories on the side bar, where that data feeds from a a json formatted API.

The issue I have is no matter what I try I cannot get rid of duplicates. As several apps are associated with the ‘Browsers’ category it lists Browsers in the list several times, where Ideally it should be listed just the once.

I have scoured forms and within a test playground I can use a removingDuplicates() extension which works with some dummy dats, but it appears anything I try to do with my actual code throws errors.

I have my code refactored but I will paste the key areas here to hopefully highlight my issue.

Any pointers would be appreciated.

// API call

enum NetworkError: Error {
    case invalidServerResponse
}

class Api {
    
    func fetchApps() async throws -> [Application] {
        
        let url = Constants.Urls.liveBlueprintURL
    
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer " + Constants.apiBearer, forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw NetworkError.invalidServerResponse
        }
        
        let application = try JSONDecoder().decode([Application].self, from: data)
        
        return application
        
    }
    
}

// Model

struct Application: Decodable {
    
    let appName: String?
    let category: String?
}

// ListViewModel

@MainActor
class CategoryListViewModel: ObservableObject {
    
    var appState: AppState
    
    init(appState: AppState) {
        self.appState = appState
    }
    
    var applications: [CategoryViewModel] {
        appState.application
    }
    
    // Function that gets the applications
    func getApps() async {
        
        do {
            // Use the Api service
            let apiCall = try await Api().fetchApps()
            appState.application = apiCall.map(CategoryViewModel.init)
            
        } catch {
            //Catch any errors if there are any
            print(error)
        }
    }
    
}

// CategoryViewModel

struct CategoryViewModel: Identifiable {
    
    private var application: Application
    
    init(application: Application) {
        self.application = application
    }
    
    let id = UUID()
    
    
    var category: String! {
        application.category
    }
   
}

// CategoryListView

struct CategoryListView: View {
    
    let applications: [CategoryViewModel]
    
    var body: some View {
        
        List {
            
            Text("Header")
            
            Divider()
            
            Spacer()
            
            ForEach(applications) { application in
                Text(application.category)
                  
            }
        }
    }
}

This is an example of the extension I used within a swift Playground using some dummy data.

extension Array where Element: Hashable {
    //Returns a new array without duplicates,
    func removingDuplicates() -> [Element] {
        var addedDict = Set<Element>()
        return filter {
            addedDict.insert($0).inserted
        }
    }
    //Modifies the array in-place to remove duplicates
    mutating func removeDuplicates() {
        self = self.removingDuplicates()
    }
}

var category = ["Browser", "Browser", "Productivity", "Productivity", "Utilities"]

let categoryList = category.removingDuplicates()

print(categoryList)

>Solution :

change your removeduplicate as follows:

extension Array where Element == CategoryViewModel {
    func removingDuplicates() -> [Element] {
        var addedDict = Set<String>()
        return filter {
            if let category = $0.category, addedDict.insert(category).inserted {
                return true
            }
            return false
        }
    }
}

and call it

var applications: [CategoryViewModel] {
    appState.application.removingDuplicates()
}

Leave a Reply