iOS UIScrollView内元素未居中偏移左侧问题求助
iOS布局元素整体左偏移问题修复方案
我编写了如下iOS布局代码,但是页面中的所有元素并未居中,整体向屏幕左侧偏移(见图):

问题代码
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 })
问题原因
UIScrollView的contentView仅设置了top/leading/trailing/bottom四个方向的约束,没有明确宽度限制。此时scrollView无法确定contentSize的宽度,会导致contentView宽度收缩,内部元素的居中约束失效,出现整体左偏移。
修复方案
修改contentView的约束,添加宽度等于scrollView所在容器的宽度的约束,有两种方式:
方式一:绑定contentView宽度到view的safeArea
把原来的contentView约束替换为:
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), // 添加这行,确保contentView宽度和屏幕安全区域一致 contentView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor) ])
方式二:绑定contentView宽度到scrollView的frame宽度
如果不需要考虑safeArea,也可以绑定到scrollView的widthAnchor:
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), // 添加这行 contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor) ])
添加完宽度约束后,scrollView就能正确计算contentSize,内部元素的居中约束会正常生效,整体布局就会居中显示。
内容的提问来源于stack exchange,提问作者Chris Hansen
相关产品推荐
相关产品推荐

