skip to Main Content

I’ve built a view that has scroll view of horizontal type with HStack for macOS app. Is there a way to circle those items using keyboard arrows?

(I see that ListView has a default behavior but for other custom view types there are none)

click here to see the screenshot

var body: some View {
   VStack {
     ScrollView(.horizontal, {
        HStack {
          ForEach(items.indices, id: .self) { index in
               //custom view for default state and highlighted state
          }
        }
     }
    }
}


any help is appreciated :)

2

Answers


  1. Approach I used

    • Uses keyboard shortcuts on a button

    Alternate approach

    Code:

    Model

    struct Item: Identifiable {
        var id: Int
        var name: String
    }
    
    class Model: ObservableObject {
        @Published var items = (0..<100).map { Item(id: $0, name: "Item ($0)")}
    }
    

    Content

    struct ContentView: View {
        
        @StateObject private var model = Model()
        @State private var selectedItemID: Int?
        
        var body: some View {
            VStack {
                Button("move right") {
                    moveRight()
                }
                .keyboardShortcut(KeyEquivalent.rightArrow, modifiers: [])
                
                
                ScrollView(.horizontal) {
                    LazyHGrid(rows: [GridItem(.fixed(180))]) {
                        ForEach(model.items) { item in
                            ItemCell(
                                item: item,
                                isSelected: item.id == selectedItemID
                            )
                            .onTapGesture {
                                selectedItemID = item.id
                            }
                        }
                    }
                }
            }
        }
        
        private func moveRight() {
            if let selectedItemID {
                if selectedItemID + 1 >= model.items.count {
                    self.selectedItemID = model.items.last?.id
                } else {
                    self.selectedItemID = selectedItemID + 1
                }
            } else {
                selectedItemID = model.items.first?.id
            }
        }
    }
    

    Cell

    struct ItemCell: View {
        let item: Item
        let isSelected: Bool
        var body: some View {
            ZStack {
                Rectangle()
                    .foregroundColor(isSelected ? .yellow : .blue)
                Text(item.name)
            }
        }
    }
    
    Login or Signup to reply.
  2. You could try this example code, using my previous post approach, but with a horizontal scrollview instead of a list. You will have to adjust the code to your particular app. My approach consists only of a few lines of code that monitors the key events.

    import Foundation
    import SwiftUI
    import AppKit
    
    struct ContentView: View {
        let fruits = ["apples", "pears", "bananas", "apricot", "oranges"]
        @State var selection: Int = 0
        @State var keyMonitor: Any?
        
        var body: some View {
            ScrollView(.horizontal) {
                HStack(alignment: .center, spacing: 0) {
                    ForEach(fruits.indices, id: .self) { index in
                        VStack {
                            Image(systemName: "globe")
                                .resizable()
                                .scaledToFit()
                                .frame(width: 20, height: 20)
                                .padding(10)
                            Text(fruits[index]).tag(index)
                        }
                        .background(selection == index ? Color.red : Color.clear)
                        .padding(10)
                    }
                }
            }
            .onAppear {
                keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { nsevent in
                    if nsevent.keyCode == 124 { // arrow right
                        selection = selection < fruits.count ? selection + 1 : 0
                    } else {
                        if nsevent.keyCode == 123 { // arrow left
                            selection = selection > 1 ? selection - 1 : 0
                        }
                    }
                    return nsevent
                }
            }
            .onDisappear {
                if keyMonitor != nil {
                    NSEvent.removeMonitor(keyMonitor!)
                    keyMonitor = nil
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search