iosswiftswiftuimobile

How can I make some horizontal lines where the selected items are placed with Swift UI like Duolingo app?


I have a question about Swift UI. I am implementing a feature of users studying a new word by placing each word in the sentence in a correct order like the picture below.

For now, the tapped words will be nicely moved and placed inside the box, which is good.

However, I feel like it would look better if there are some horizontal lines instead of the big box and those selected words will be placed on those lines just like Duolingo. Here are the codes, so that would be great if you can help me implement that change.

Thank you in advance.

ScrollView{
    VStack(alignment: .leading){
        ForEach(0..<(arrangedWords.count + itemsPerRow - 1) / itemsPerRow, id: \.self) { row in
            HStack(spacing: 8) {
                ForEach(0..<itemsPerRow, id: \.self) { word in
                    let index = row * itemsPerRow + word
                    if index < arrangedWords.count {
                        let targetWord = arrangedWords[index]
                        Text(targetWord)
                            .foregroundColor(.white)
                            .padding()
                            .background(Rectangle().fill(Color.green.opacity(0.8)))
                            .cornerRadius(10)
                            .animation(.spring(), value: arrangedWords)
                            .onTapGesture {
                                moveWordOutOfSpace(index: index)
                                print("arranged words: \(arrangedWords)")
                            }
                    }
                }
            }
        }
    }
    .frame(maxWidth: .infinity, alignment: .leading)
    .frame(minHeight: 100)
    .padding(10)
}
.background(.white)
.cornerRadius(10)
.overlay {
    RoundedRectangle(cornerRadius: 10)
        .stroke(Color.gray, lineWidth: 5)
}
.padding(.horizontal,10)

enter image description here enter image description here


Solution

  • One way to add lines is to add them to the background of the HStack used for the rows.

    HStack(spacing: 8) {
        ForEach(0..<itemsPerRow, id: \.self) { word in
            // ...
        }
    }
    .frame(maxWidth: .infinity, alignment: .leading)
    .background(alignment: .bottom) {
        Color.blue
            .frame(height: 2)
    }
    

    Screenshot

    If you want the lines to be visible before any words are shown, hidden placeholders can be used as a substitute for the words.

    private let minNumLines = 2
    
    VStack(alignment: .leading){
        let nLines = max(minNumLines, (arrangedWords.count + itemsPerRow - 1) / itemsPerRow)
        ForEach(0..<nLines, id: \.self) { row in
            HStack(spacing: 8) {
                ForEach(0..<itemsPerRow, id: \.self) { word in
                    let index = row * itemsPerRow + word
                    if index < arrangedWords.count {
                        let targetWord = arrangedWords[index]
                        Text(targetWord)
                            // + modifiers, as before
                    } else if word == 0 {
                        Text(".")
                            .hidden()
                            .padding()
                    }
                }
            }
            // + modifiers, as above
        }
    }
    

    Screenshot