iosswift

Elements not centered on the page


I have the following code

    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.showsHorizontalScrollIndicator = false
    scrollView.showsVerticalScrollIndicator = false
    view.addSubview(scrollView)
    
    NSLayoutConstraint.activate([
        scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    
    let contentView = UIView()
    contentView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.addSubview(contentView)
    
    contentView.backgroundColor = UIColor(red: 0.97, green: 0.98, blue: 0.99, alpha: 1.00)
    
    NSLayoutConstraint.activate([
        contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
        contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
        contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
        contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
    ])
    
    // Large Label
    let largeLabel: UILabel = {
        let label = UILabel()
        label.text = "Congrats!"
        label.font = UIFont(name: "Inter-Bold", size: 24)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    // Small Label
    let smallLabel: UILabel = {
        let label = UILabel()
        label.text = "Your finished the \(category.name) lesson."
        label.font = UIFont(name: "Inter-Regular", size: 16)
        label.textColor = .black
        label.textAlignment = .center
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    
    // Image View
    let animatedImageView: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "fire"))
        imageView.tintColor = .systemYellow
        imageView.contentMode = .scaleAspectFit
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    
    contentView.addSubview(largeLabel)
    contentView.addSubview(smallLabel)
    contentView.addSubview(animatedImageView)
    
    
    let totalScore = score + WUser.sharedInstance.getUserScore()
    var nextLevel = ""
    var currentLevel = ""
    var toXP = 0
    
    let pointsView = UIView()
    pointsView.layer.cornerRadius = 20
    pointsView.backgroundColor = .white
    pointsView.layer.borderColor = UIColor.gray.cgColor
    pointsView.layer.borderWidth = 1
    pointsView.layer.masksToBounds = true
    pointsView.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(pointsView)
    
    let pointsLabel: UILabel = {
        let label = UILabel()
        label.text = "Points Earned"
        label.font = UIFont(name: "Inter-Bold", size: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let percentageLabel: UILabel = {
        let label = UILabel()
        label.text = "+\(score + 5)"
        label.font = UIFont(name: "Inter-Bold", size: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let iconImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.image = UIImage(systemName: "star.fill")
        imageView.tintColor = UIColor(red: 0.63, green: 0.14, blue: 0.92, alpha: 1.00)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
       
    let progressView = UIProgressView(progressViewStyle: .default)
    progressView.progress = 0
    progressView.layer.cornerRadius = 10
    progressView.layer.masksToBounds = true
    progressView.trackTintColor = UIColor(red: 0.91, green: 0.91, blue: 0.91, alpha: 1.00)
    progressView.progressTintColor = UIColor(red: 0.63, green: 0.14, blue: 0.92, alpha: 1.00)
    progressView.translatesAutoresizingMaskIntoConstraints = false

    
    if totalScore < 5 {
        currentLevel = "Level 1"
        nextLevel = "Starter"
        toXP = 5 - totalScore
    } else if totalScore < 50 {
        currentLevel = "Starter"
        nextLevel = "Trailblazer"
        toXP = 50 - totalScore
    } else if totalScore < 100 {
        currentLevel = "Trailblazer"
        nextLevel = "Champ"
        toXP = 100 - totalScore
    } else if totalScore < 200 {
        currentLevel = "Champ"
        nextLevel = "Genius"
        toXP = 200 - totalScore
    } else if totalScore < 500 {
        currentLevel = "Champ"
        nextLevel = "Genius"
        toXP = 500 - totalScore
    } else {
        currentLevel = "Genius"
        nextLevel = "Genius"
    }
    
    
    let xpLabel: UILabel = {
        let label = UILabel()
        label.text = "\(toXP) XP to \(nextLevel)"
        label.font = UIFont(name: "Inter-Regular", size: 14)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    pointsView.addSubview(pointsLabel)
    pointsView.addSubview(percentageLabel)
    pointsView.addSubview(iconImageView)
    contentView.addSubview(progressView)
    contentView.addSubview(xpLabel)
    
    NSLayoutConstraint.activate([
        largeLabel.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 20),
        largeLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
        largeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
        
        // Small Label Constraints
        smallLabel.topAnchor.constraint(equalTo: largeLabel.bottomAnchor, constant: 20),
        smallLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
        smallLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
        
        animatedImageView.topAnchor.constraint(equalTo: smallLabel.bottomAnchor, constant: 40),
        animatedImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        animatedImageView.widthAnchor.constraint(equalToConstant: 150),
        animatedImageView.heightAnchor.constraint(equalToConstant: 150),
            
        pointsView.topAnchor.constraint(equalTo: animatedImageView.bottomAnchor, constant: 50),
        pointsView.widthAnchor.constraint(equalToConstant: 300),
        pointsView.heightAnchor.constraint(equalToConstant: 50),
        pointsView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        
        pointsLabel.leadingAnchor.constraint(equalTo: pointsView.leadingAnchor, constant: 16),
        pointsLabel.centerYAnchor.constraint(equalTo: pointsView.centerYAnchor),
        
        percentageLabel.trailingAnchor.constraint(equalTo: iconImageView.leadingAnchor, constant: -8),
        percentageLabel.centerYAnchor.constraint(equalTo: pointsView.centerYAnchor),
        
        iconImageView.trailingAnchor.constraint(equalTo: pointsView.trailingAnchor, constant: -16),
        iconImageView.centerYAnchor.constraint(equalTo: pointsView.centerYAnchor),
        iconImageView.widthAnchor.constraint(equalToConstant: 24),
        iconImageView.heightAnchor.constraint(equalToConstant: 24),
        
        
        progressView.leadingAnchor.constraint(equalTo: pointsView.leadingAnchor, constant: 16),
        progressView.trailingAnchor.constraint(equalTo: pointsView.trailingAnchor, constant: -16),
        progressView.topAnchor.constraint(equalTo: pointsView.bottomAnchor, constant: 30),
        progressView.heightAnchor.constraint(equalToConstant: 20),
        
        xpLabel.leadingAnchor.constraint(equalTo: progressView.leadingAnchor),
        xpLabel.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: 10)
    ])
    
    
    let instructionLabel = UILabel()
    instructionLabel.text = "How would you rate the lesson?"
    instructionLabel.textAlignment = .center
    instructionLabel.font = UIFont(name: "Inter-Bold", size: 18)
    instructionLabel.translatesAutoresizingMaskIntoConstraints = false
    
    let starsStackView = UIStackView()
    starsStackView.axis = .horizontal
    starsStackView.alignment = .center
    starsStackView.distribution = .equalSpacing
    starsStackView.spacing = 10
    starsStackView.translatesAutoresizingMaskIntoConstraints = false
    
    for i in 1...5 {
        let button = UIButton(type: .system)
        button.tag = i
        button.setTitle("★", for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 30)
        button.setTitleColor(.lightGray, for: .normal)
        button.addTarget(self, action: #selector(starTapped(_:)), for: .touchUpInside)
        starsStackView.addArrangedSubview(button)
    }
    
    let submitButton = UIButton(type: .system)
    submitButton.setTitle("Submit Rating", for: .normal)
    submitButton.backgroundColor = UIColor(red: 0.35, green: 0.25, blue: 0.55, alpha: 1.00)
    submitButton.setTitleColor(.white, for: .normal)
    submitButton.layer.cornerRadius = 20
    submitButton.titleLabel?.font = UIFont(name: "Inter-Bold", size: 16)
    submitButton.addTarget(self, action: #selector(submitRating), for: .touchUpInside)
    submitButton.translatesAutoresizingMaskIntoConstraints = false
    
    
    let closeButton = UIButton(type: .system)
    closeButton.setTitle("Close", for: .normal)
    closeButton.setTitleColor(.gray, for: .normal)
    closeButton.titleLabel?.font = UIFont(name: "Inter-Bold", size: 16)
    closeButton.addTarget(self, action: #selector(goBack), for: .touchUpInside)
    closeButton.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(instructionLabel)
    contentView.addSubview(starsStackView)
    contentView.addSubview(submitButton)
    contentView.addSubview(closeButton)
    
    NSLayoutConstraint.activate([
        instructionLabel.topAnchor.constraint(equalTo: xpLabel.bottomAnchor, constant: 30),
        instructionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        
        starsStackView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20),
        starsStackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        
        submitButton.topAnchor.constraint(equalTo: starsStackView.bottomAnchor, constant: 20),
        submitButton.widthAnchor.constraint(equalToConstant: 250),
        submitButton.heightAnchor.constraint(equalToConstant: 50),
        submitButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        
        
        closeButton.topAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: 20),
        closeButton.widthAnchor.constraint(equalToConstant: 250),
        closeButton.heightAnchor.constraint(equalToConstant: 50),
        closeButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        closeButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0)
    ])
    
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        UIView.animate(withDuration: 1.5) {
            progressView.setProgress(Float(Float(totalScore)/500), animated: true)
        }
    }
    
    UIView.animate(withDuration: 1.0,
                   delay: 0,
                   options: [.autoreverse, .repeat],
                   animations: {
        animatedImageView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
    }, completion: { _ in
        animatedImageView.transform = .identity
    })

Unfortunately, the elements are not centered on the page. They shift to the left of the screen.

See attached

Can someone please help?

enter image description here


Solution

  • I am pretty sure it appears like this because contentView's anchors aren't setup correctly. In your code, you have this constraint:

    contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
    

    This means that the right edge of contentView is pinned to the right edge of scrollView, but it does not guarantee that contentView will always match the width of scrollView.

    If contentView's content expands beyond scrollView.width, scrollView will allow horizontal scrolling. If contentView's content is smaller than scrollView.width, contentView won't stretch automatically, and the content might not appear properly aligned to the right.

    The correct way to setup constraints is

    NSLayoutConstraint.activate([
        contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
        contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
                contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
        contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
    ])