When trying to create the illusion of movement with a tiled background image, I noticed that the image gets cut off when I shift it. This happens because the offset moves parts of the image outside of the view’s boundaries, exposing gaps. Also, the image seems to get dimmed when I press the move button. I want the image to shift across the screen seamlessly, without any gaps or dimming, and to keep the tiling consistent across the entire screen.
import SwiftUI
struct ContentView: View {
@State private var offset: CGFloat = 0.0
var body: some View {
ZStack {
Image("carrot")
.resizable(resizingMode: .tile)
.offset(x: offset)
VStack {
Spacer()
HStack {
Button("<<<<<") {
offset -= 50.0
}
Button(">>>>>") {
offset += 50.0
}
}
.buttonStyle(.borderedProminent)
.padding()
}
}
.animation(.easeInOut, value: offset)
}
}
When trying to create the illusion of movement with a tiled background image, I noticed that the image gets cut off when I shift it. This happens because the offset moves parts of the image outside of the view’s boundaries, exposing gaps. Also, the image seems to get dimmed when I press the move button. I want the image to shift across the screen seamlessly, without any gaps or dimming, and to keep the tiling consistent across the entire screen.
import SwiftUI
struct ContentView: View {
@State private var offset: CGFloat = 0.0
var body: some View {
ZStack {
Image("carrot")
.resizable(resizingMode: .tile)
.offset(x: offset)
VStack {
Spacer()
HStack {
Button("<<<<<") {
offset -= 50.0
}
Button(">>>>>") {
offset += 50.0
}
}
.buttonStyle(.borderedProminent)
.padding()
}
}
.animation(.easeInOut, value: offset)
}
}
Share
Improve this question
edited Jan 18 at 11:19
Benzy Neez
22.5k3 gold badges15 silver badges43 bronze badges
asked Jan 18 at 4:33
MangoMango
556 bronze badges
0
1 Answer
Reset to default 0When an area is filled using a tiled image, the tiling always starts in the top-left corner. So if you want to have the effect of an offset, it means moving the starting position for the tiling.
A move to the left is easy: just add negative padding to the image. There is then no need to apply a negative x-offset as well.
Moving to the right is much harder. There needs to be a "buffer region" that is off-screen. This is then moved into view with a positive x-offset.
The size of the buffer region needs to be an exact multiple of the image width. If the image size is not known then it can be measured using a hidden version of the image in the background of the ZStack
.
The width of the display area also needs to be known. This can be measured using a GeometryReader
.
Finally, you need to control which changes are animated and which are not. This can be done with a combination of .animation
modifiers and the modifier .geometryGroup
.
Here is an adaption of your example that works this way:
struct ContentView: View {
@State private var offset: CGFloat = 0.0
@State private var imageWidth = CGFloat.zero
var body: some View {
ZStack {
GeometryReader { proxy in
let fullWidth = proxy.size.width
let nExtraImages = imageWidth > 0 ? (max(0, offset) / imageWidth).rounded(.up) : 0
let extendedWidth = fullWidth + ((nExtraImages + 1) * imageWidth)
Image("carrot")
.resizable(resizingMode: .tile)
.padding(.leading, min(0, offset))
.frame(width: extendedWidth)
.animation(nil, value: extendedWidth)
.frame(width: fullWidth, alignment: .trailing)
.geometryGroup()
.offset(x: max(0, offset))
.animation(.easeInOut, value: offset)
}
VStack {
Spacer()
HStack {
Button("<<<<<") {
offset -= 50.0
}
Button(">>>>>") {
offset += 50.0
}
}
.buttonStyle(.borderedProminent)
.padding()
}
}
.background {
Image("carrot")
.hidden()
.onGeometryChange(for: CGFloat.self) { proxy in
proxy.size.width
} action: { width in
imageWidth = width
}
}
}
}
Regarding dimming, I did not see this problem happening. However, it may be related to the way the image is being prepared for render. So it may help to add versions of the image with different resolutions to your asset catalogue (something like: carrot.png, [email protected], [email protected]).
Btw, if your ultimate objective is to show the tiles moving in the background with a continuous animation then I would not use an ever-increasing offset to do it. Use a repeating animation instead.