Search code examples
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.

    • A line can be drawn as a Color with a (very small) fixed height.

    • Use alignment: .bottom when adding the background.

    • A color will automatically use all the space available, so it will stretch the full width of the HStack.

    • If you want the lines to stretch the full width of the bounded area, this means increasing the width of the HStack to the maximum width possible. This is done by adding a .frame modifier to the HStack, with alignment: .leading.

    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