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)
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)
}
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
}
}