You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何将文本居中显示在黑色覆盖层内?

解决SwiftUI文本在渐变覆盖层内的居中定位问题

我在SwiftUI里遇到文本定位问题,没法把白色文本居中显示在底部的渐变覆盖层里,想要文本对齐到下图红线标记的黑框中部,试了各种对齐方式和padding设置都没达到预期效果。

文本居中参考效果

相关代码片段

image
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: customSize.width, height: customSize.height)
    .clipped()
    .overlay(
        VStack(alignment: .leading) {
            Spacer()
            HStack {
                VStack(alignment: .leading) {
                    if let name = channel.tvgName {
                        Text(formatTitle(name))
                            .font(.title3.bold())
                            .foregroundColor(.white)
                            .lineLimit(1)
                            .shadow(radius: 5)
                            .padding(.all, 0)
                    } else {
                        Text(formatTitle(channel.tvgId ?? ""))
                            .font(.title3.bold())
                            .foregroundColor(.white)
                            .lineLimit(1)
                            .shadow(radius: 5)
                            .padding(.all, 0)
                    }
                }
                
                Spacer()
            }
            .padding([.leading, .bottom, .trailing], 50)
            .background(LinearGradient(
                gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.1)]),
                startPoint: .bottom,
                endPoint: .top
            ))
        }
    )
    .cornerRadius(10)
    .shadow(radius: 5)

完整视图代码

import SwiftUI
import CachedAsyncImage

struct ChannelDetailsView: View {
    var channel: PlaylistItem
    var reader: GeometryProxy
    @State var customSize: CGSize = CGSize(width: 256, height: 418)
    @State private var showByGroup = UserDefaults.standard.bool(forKey: "showByGroup")

    var body: some View {
        VStack(spacing: 0) {
            if let logo = channel.tvgLogo {
                CachedAsyncImage(url: URL(string: logo)) { phase in
                    switch phase {
                    case .empty:
                        ProgressView()
                            .frame(maxWidth: .infinity, maxHeight: .infinity)
                            .background(Color.gray.opacity(0.3))
                            .cornerRadius(10)
                            .frame(width: customSize.width,
                                   height: customSize.height)
                    case .success(let image):
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: customSize.width, height: customSize.height)
                            .clipped()
                            .overlay(
                                VStack(alignment: .leading) {
                                    Spacer()
                                    HStack {
                                        VStack(alignment: .leading) {
                                            if let name = channel.tvgName {
                                                Text(formatTitle(name))
                                                    .font(.title3.bold())
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5)
                                                    .padding(.all, 0)
                                            } else {
                                                Text(formatTitle(channel.tvgId ?? ""))
                                                    .font(.title3.bold())
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5)
                                                    .padding(.all, 0)
                                            }
                                        }
                                        
                                        Spacer()
                                    }
                                    .padding([.leading, .bottom, .trailing], 50)
                                    .background(LinearGradient(
                                        gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.1)]),
                                        startPoint: .bottom,
                                        endPoint: .top
                                    ))
                                }
                            )
                            
                            .cornerRadius(10)
                            .shadow(radius: 5)
                            
                    case .failure:
                        Image(systemName: "play.display")
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 50, height: 50)
                            .padding()
                            .background(Color.gray.opacity(0.3))
                            .cornerRadius(10)
                            .frame(width: customSize.width,
                                   height: customSize.height)
                    @unknown default:
                        EmptyView()
                    }
                }
            }
        }
        .onAppear(perform: {
            customSize = reader.size
        })
        .frame(width: customSize.width, height: customSize.height)
        .background(Color.black.opacity(0.2))
        .cornerRadius(10)
        .shadow(radius: 5)
        .onAppear {
            print("width: \(reader.size.width)")
            print("height: \(reader.size.height)")
        }
        .onChange(of: reader.size) { oldValue, newValue in
            customSize = newValue
        }
    }

    func formatTitle(_ title: String) -> String {
        var formattedTitle = title
        
        formattedTitle = formattedTitle.replacingOccurrences(of: "4K", with: "", options: .caseInsensitive)
        
        let regex = try! NSRegularExpression(pattern: "\\(\\d{4}\\)", options: [])
        formattedTitle = regex.stringByReplacingMatches(in: formattedTitle, options: [], range: NSRange(location: 0, length: formattedTitle.count), withTemplate: "")
        
        formattedTitle = formattedTitle.trimmingCharacters(in: .whitespacesAndNewlines)
        
        return formattedTitle
    }
}

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(
            roundedRect: rect,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        )
        return Path(path.cgPath)
    }
}

解决方案

问题根源在于嵌套布局的对齐设置冗余,以及过度padding挤压文本空间,调整方式如下:

修改后的核心代码片段

image
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: customSize.width, height: customSize.height)
    .clipped()
    .overlay(
        VStack {
            Spacer()
            HStack {
                Text(formatTitle(channel.tvgName ?? channel.tvgId ?? ""))
                    .font(.title3.bold())
                    .foregroundColor(.white)
                    .lineLimit(1)
                    .shadow(radius: 5)
                    .frame(maxWidth: .infinity, alignment: .center)
            }
            .padding([.leading, .bottom, .trailing], 20)
            .background(LinearGradient(
                gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.1)]),
                startPoint: .bottom,
                endPoint: .top
            ))
        }
    )
    .cornerRadius(10)
    .shadow(radius: 5)

关键调整点

  1. 移除外层VStack的.leading对齐,保留Spacer确保渐变层贴在图片底部
  2. 简化文本判断逻辑,用channel.tvgName ?? channel.tvgId ?? ""合并分支
  3. 给Text添加.frame(maxWidth: .infinity, alignment: .center),强制文本在渐变层宽度范围内居中
  4. 缩小渐变层的padding值(从50改为20,可按需调整),避免内边距过大导致文本偏移

内容的提问来源于stack exchange,提问作者Damiano Miazzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:44:56