How to make List with single selection with SwiftUI 5

The easiest way to achieve this would be to have @State in the View containing the list with the selection and pass it as @Binding to the cells:

struct SelectionView: View {

    let fruit = ["apples", "pears", "bananas", "pineapples"]
    @State var selectedFruit: String? = nil

    var body: some View {
        List {
            ForEach(fruit, id: \.self) { item in
                SelectionCell(fruit: item, selectedFruit: self.$selectedFruit)
            }
        }
    }
}

struct SelectionCell: View {

    let fruit: String
    @Binding var selectedFruit: String?

    var body: some View {
        HStack {
            Text(fruit)
            Spacer()
            if fruit == selectedFruit {
                Image(systemName: "checkmark")
                    .foregroundColor(.accentColor)
            }
        }   .onTapGesture {
                self.selectedFruit = self.fruit
            }
    }
}

Selection

SwiftUI does not currently have a built in way to select one row of a list and change its appearance accordingly. But you're actually very close to your answer. In fact, your selection is in fact already working, but just isn't being used in any way.

To illustrate, add the following line right after ModuleCell(...) in your ForEach:

.background(i == self.selectionKeeper ? Color.red : nil)

In other words, "If my current row (i) matches the value stored in selectionKeeper, color the cell red, otherwise, use the default color." You'll see that as you tap on different rows, the red coloring follows your taps, showing the underlying selection is in fact changing.

Deselection

If you wanted to enable deselection, you could pass in a Binding<Int?> as your selection, and set it to nil when the currently selected row is tapped:

struct ModuleList: View {
    var modules: [Module] = []
    // this is new ------------------v
    @Binding var selectionKeeper: Int?
    var Action: () -> Void

    // this is new ----------------------------v
    init(list: [Module], selection: Binding<Int?>, action: @escaping () -> Void) {

    ...

    func changeSelection(index: Int){
        if selectionKeeper != index {
            selectionKeeper = index
        } else {
            selectionKeeper = nil
        }
        self.Action()
    }
}

Deduplicating State and Separation of Concerns

On a more structural level, you really want a single source of truth when using a declarative UI framework like SwiftUI, and to cleanly separate your view from your model. At present, you have duplicated state — selectionKeeper in ModuleList and isSelected in Module both keep track of whether a given module is selected.

In addition, isSelected should really be a property of your view (ModuleCell), not of your model (Module), because it has to do with how your view appears, not the intrinsic data of each module.

Thus, your ModuleCell should look something like this:

struct ModuleCell: View {
    var module: Module
    var isSelected: Bool // Added this
    var Action: () -> Void

    // Added this -------v
    init(module: Module, isSelected: Bool, action: @escaping () -> Void) {
        UITableViewCell.appearance().backgroundColor = .clear
        self.module = module
        self.isSelected = isSelected  // Added this
        self.Action = action
    }

    var body: some View {
        Button(module.name, action: {
            self.Action()
        })
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
            .modifier(Constants.CellSelection(isSelected: isSelected))
            // Changed this ------------------------------^
    }
}

And your ForEach would look like

ForEach(0..<modules.count) { i in
    ModuleCell(module: self.modules[i],
               isSelected: i == self.selectionKeeper,
               action: { self.changeSelection(index: i) })
}

Here is a more generic approach, you can still extend answer according to your needs;

TLDR
https://gist.github.com/EnesKaraosman/d778cdabc98ca269b3d162896bea8aac


Detail

struct SingleSelectionList<Item: Identifiable, Content: View>: View {
    
    var items: [Item]
    @Binding var selectedItem: Item?
    var rowContent: (Item) -> Content
    
    var body: some View {
        List(items) { item in
            rowContent(item)
                .modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
                .contentShape(Rectangle())
                .onTapGesture {
                    self.selectedItem = item
                }
        }
    }
}

struct CheckmarkModifier: ViewModifier {
    var checked: Bool = false
    func body(content: Content) -> some View {
        Group {
            if checked {
                ZStack(alignment: .trailing) {
                    content
                    Image(systemName: "checkmark")
                        .resizable()
                        .frame(width: 20, height: 20)
                        .foregroundColor(.green)
                        .shadow(radius: 1)
                }
            } else {
                content
            }
        }
    }
}

And to demonstrate;

struct PlaygroundView: View {
    
    struct Koko: Identifiable {
        let id = UUID().uuidString
        var name: String
    }
    
    var mock = Array(0...10).map { Koko(name: "Item - \($0)") }
    @State var selectedItem: Koko?
    
    
    var body: some View {
        VStack {
            Text("Selected Item: \(selectedItem?.name ?? "Select one")")
            Divider()
            SingleSelectionList(items: mock, selectedItem: $selectedItem) { (item) in
                HStack {
                    Text(item.name)
                    Spacer()
                }
            }
        }
    }
    
}

Final Result
enter image description here