swiftuioverlaytext-alignmentui-design

"How to Center Text in the Middle of a Black Overlay in SwiftUI?"


I'm having trouble positioning text in the center of a black overlay in SwiftUI. I want the white text to be aligned in the middle of the black box where I marked with a red line in the image below. Despite trying different alignments and padding, I can't seem to get the text to appear where I want it.

Here's the image showing where I want the text to be:


  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()) // Adjust the font size and style
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5) // Add shadow for better contrast
                                                    .padding(.all, 0)
                                            } else {
                                                Text(formatTitle(channel.tvgId ?? ""))
                                                    .font(.title3.bold()) // Adjust the font size and style
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5) // Add shadow for better contrast
                                                    .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)
                            

Full view code:

import SwiftUI
import CachedAsyncImage

struct ChannelDetailsView: View {
    var channel: PlaylistItem
    var reader: GeometryProxy
    @State var customSize: CGSize = CGSize(width: 256, height: 418) // Updated size
    @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()) // Adjust the font size and style
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5) // Add shadow for better contrast
                                                    .padding(.all, 0)
                                            } else {
                                                Text(formatTitle(channel.tvgId ?? ""))
                                                    .font(.title3.bold()) // Adjust the font size and style
                                                    .foregroundColor(.white)
                                                    .lineLimit(1)
                                                    .shadow(radius: 5) // Add shadow for better contrast
                                                    .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
        }
    }

    // Function to format the title
    func formatTitle(_ title: String) -> String {
        var formattedTitle = title
        
        // Remove "4K"
        formattedTitle = formattedTitle.replacingOccurrences(of: "4K", with: "", options: .caseInsensitive)
        
        // Remove year in parentheses, e.g., "(2021)"
        let regex = try! NSRegularExpression(pattern: "\\(\\d{4}\\)", options: [])
        formattedTitle = regex.stringByReplacingMatches(in: formattedTitle, options: [], range: NSRange(location: 0, length: formattedTitle.count), withTemplate: "")
        
        // Remove any extra spaces
        formattedTitle = formattedTitle.trimmingCharacters(in: .whitespacesAndNewlines)
        
        return formattedTitle
    }
}

// Extension to apply corner radius to specific corners
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)
    }
}

image


Solution

  • Instead of using stacks and spacers to align things, you can just pass an alignment to the overlay modifier. The Text will be automatically put on the bottom centre of the image.

    Then all you need to do is add some vertical padding on the Text, and do .frame(maxWidth: .infinity) so that it occupies the full width of the image. Finally, you add the gradient background and the text will be centered inside that.

    Image("some image")
        .resizable()
        .scaledToFit()
        .overlay(alignment: .bottom) {
            Text("The Instigators")
                .font(.title3.bold())
                .foregroundStyle(.white)
                .lineLimit(1)
                .shadow(radius: 5)
                .padding(.vertical, 20)
                .frame(maxWidth: .infinity)
                .background(.linearGradient(
                    colors: [.blue, .blue.opacity(0.1)],
                    startPoint: .bottom,
                    endPoint: .top
                ))
        }
        .padding()
    

    I see that you are using a lot of deprecated view modifiers. In case you need to support versions below iOS 15, the alignment: argument for overlay is the second argument before iOS 15.

    .overlay(
        Text("The Instigators")
            .font(.title3.bold())
            .foregroundColor(.white)
            // ...
            ,
        alignment: .bottom
    )
    

    Last but not least, consider replacing the if let statement - you can just do:

    Text(formatTitle(channel.tvgName ?? channel.tvgId ?? ""))