Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to align a view bottom trailing, overlaid on another view that clips over the edge of the screen in SwiftUI?

I have a view which consists of a background image/animation and a foreground control buttons. I want to .overlay the buttons on top of the background, to be aligned at the bottom leading, center, and trailing - see screenshot below (doesn't show the actual background).

However, the background has to be an animation. I'm using a wrapper view around AVPlayer for this. The video that gets played is portrait, but it gets filled to landscape as is thus wider than screen. I need it to fill the vertical space of the screen. That means that I have to use .frame(maxWidth: .infinity, maxHeight: .infinity).scaledToFill().

That leaves vertical alignment just fine - the gray top-center badge and the bottom-center button are rendered at the right places - however, it messes with the horizontal alignment. The bottom-right button aligns itself w.r.t. the video instead of the screen, and is thus aligned out of the bounds of the screen (see the second image).

I need the background to fill the screen, and the overlay to be aligned properly. The videos are recorded portrait, but AVPlayer makes them landscape with black filling on the sides, so unless that can be tweaked, I can't change the videos' aspect-ratios.

The most beneficial thing for me would be to learn how to align components w.r.t. the screen, not the parent in the overlay stack. Is there are way to achieve that? If not, is there a workaround to fix my problem (make the buttons align properly along the horizontal)?

Code

The code below isn't the source of truth, and just generates the demos. It is provided since a gentleman in the comments politely asked for it. The images are the ultimate source of truth. The actual code is much bigger, with a mechanism of randomly (and based on app state) choosing an AVPlayer to play an mp4 video. I don't think that this should be important (encapsulation and stuff), but if it is, tell me why it affects the code structure in the comments, and I will add more details.

A function used in the demos below is:

private func bigBgImage() -> some View {
    self.backgroundChooser.render()
        .resizable()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .scaledToFill()
}

...where backgroundChooser.render() is to be taken as a blackbox. You can just drop in a dummy Image.

Desired layout

desired overlay layout

This demo is achieved through a dummy image in a GeometryReader. This is a workaround to bound the overlays despite the bigBgImage having maxWidth: .infinity. Since GeometryReader messes with the animation alignment, I don't want to use it in the final code. Nevertheless, here's the snippet:

GeometryReader { _ in
    self.bigBgImage()
}
    .overlay(alignment: .top) { self.title() }
    .overlay(alignment: .bottom) { self.resetButton() }
    .overlay(alignment: .bottomTrailing) { self.toggleButton() }

Undesired layout

overflow messes up horizontal align

self.bigBgImage()
    .overlay(alignment: .top) { self.title() }
    .overlay(alignment: .bottom) { self.resetButton() }
    .overlay(alignment: .bottomTrailing) { self.toggleButton() }

The mechanism behind backgroundChooser is too complex to contain in an SO question in a meaningful way. One can just drop in any image

like image 373
ruza-net Avatar asked Sep 20 '25 16:09

ruza-net


1 Answers

I found that the problem was your .frame and .scaleToFill() after the Image.

Here I have a different solution. (Code is below the image) enter image description here

Use screen max width and height instead of .infinity with overlay

struct DemoView: View {
var body: some View {
    ZStack {
        bigBigImage
            .overlay(alignment: .top) {
                Button("Top") {
                }
                .padding()
                .background(.black)
                .cornerRadius(10)
            }
            .overlay(alignment: .bottom) {
                Button("Center") {
                }
                .padding()
                .background(.black)
                .cornerRadius(10)
            }
            .overlay(alignment: .bottomTrailing) {
                Button("Leading") {
                }
                .padding()
                .background(.black)
                .cornerRadius(10)
                .padding(.trailing)
            }
     }
   }
    var bigBigImage: some View {
     Image("Swift")
        .resizable()
        .scaledToFill() //here
        .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height) //here
        .edgesIgnoringSafeArea(.all)
    }
}
like image 118
Steven-Carrot Avatar answered Sep 22 '25 05:09

Steven-Carrot