How to center a viewAligned ScrollView in SwiftUI
By default a ScrollView with .scrollTargetBehavior(.viewAligned) is aligned to the leading edge. To get a .viewAligned ScrollView centered, you can use combination of .containerRelativeFrame, constant spacing, and optionally .contentMargins and .safeAreaPadding.
 
                    In SwiftUI, by default a ScrollView with  .scrollTargetBehavior(.viewAligned) is aligned to the leading edge.
To get a .viewAligned ScrollView centered, you can use combination of .containerRelativeFrame, constant spacing, and optionally .contentMargins and .safeAreaPadding.
Let's start with a basic code sample that demonstrates the default leading-edge alignment:
import SwiftUI
/// Card Model
struct Card: Identifiable {
    var id: Int
}
// Use the same spacing value with all modifiers
var hSpacing: CGFloat { 10.0 }
struct ContentView: View {
    // Create 30 Cards
    var cards: [Card] = (1..<30).map { Card(id: $0) }
    
    var body: some View {
        VStack {
            ScrollView(.horizontal, showsIndicators: false) {
                LazyHStack(alignment: .center, spacing: hSpacing) {
                    ForEach(cards) { card in
                        CardView(card: card)
                        .aspectRatio(16.0/9.0, contentMode: .fit)
                    }
                }
                .frame(height: 80)
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.viewAligned)
        }
    }
}
/// Single Card View
struct CardView: View {
    let card: Card
    
    var body: some View {
        RoundedRectangle(cornerRadius: 20)
            .foregroundStyle(.blue.gradient)
            .overlay {
                Text(card.id, format: .number)
                    .foregroundStyle(.background)
                    .font(.largeTitle)
            }
    }
}The resulting ScrollView looks like this:

To center the ScrollView, we can add the .containerRelativeFrame modifier:
.containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: hSpacing)The count and span parameters in .containerRelativeFrame can be confusing at first. You can use different combinations to show 1 and 1/2 cards, or any other fraction. This modifier is super useful!
ScrollView(.horizontal, showsIndicators: false) {
                LazyHStack(alignment: .center, spacing: hSpacing) {
                    ForEach(cards) { card in
                        CardView(card: card)
                            .aspectRatio(16.0/9.0, contentMode: .fit)
                            .containerRelativeFrame(
                                .horizontal, 
                                count: 3, 
                                span: 1, 
                                spacing: hSpacing
                            )
                    }
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.viewAligned)Make sure to use the same spacing with all view modifiers. For this we created a constanthSpacingin the first code sample:var hSpacing: CGFloat { 10.0 }
The ScrollView now scrolls centered:

To have the cards peek into the ScrollView on the leading and trailing edges, we can use the .safeAreaPadding and .contentMargins modifiers. This gives a nice effect and indicates to the user that there is more content to scroll to:
ScrollView(.horizontal, showsIndicators: false) {
                LazyHStack(alignment: .center, spacing: hSpacing) {
                    ForEach(cards) { card in
                        CardView(card: card)
                            .aspectRatio(16.0/9.0, contentMode: .fit)
                            .containerRelativeFrame(
                            .horizontal, 
                            count: 3, 
                            span: 1, 
                            spacing: hSpacing
                            )
                    }
                }
                .scrollTargetLayout()
            }
            .safeAreaPadding(hSpacing)
            .contentMargins(hSpacing)
            .scrollTargetBehavior(.viewAligned)
You can change the count parameter in .containerRelativeFrame to another odd number to show more cards:
.containerRelativeFrame(.horizontal, count: 5, span: 1, spacing: hSpacing)