Xcode项目中自定义UIButton为何出现畸变并显示两层重叠边框?
问题描述
我创建了带图片的自定义UIButton实例,运行项目时按钮出现两层边框,视觉上和两个按钮叠放效果一致。
初始按钮实现代码
private lazy var addPhotoButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "plus_photo"), for: .normal) button.tintColor = .white button.addTarget(self, action: #selector(handleAddPhotoButtonClicked), for: .touchUpInside) button.layer.cornerRadius = 128.0 / 2.0 button.setDimensions(width: 128.0, height: 128.0) button.layer.masksToBounds = true button.contentMode = .scaleAspectFill button.imageView?.clipsToBounds = true button.layer.borderColor = UIColor.white.cgColor button.layer.borderWidth = 1.0 return button }()
运行效果

完整视图控制器代码
import UIKit class RegistrationController: UIViewController { // MARK: - Properties private lazy var addPhotoButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "plus_photo"), for: .normal) button.tintColor = .white button.addTarget(self, action: #selector(handleAddPhotoButtonClicked), for: .touchUpInside) button.layer.cornerRadius = 128.0 / 2.0 button.setDimensions(width: 128.0, height: 128.0) button.layer.masksToBounds = true button.contentMode = .scaleAspectFill button.imageView?.clipsToBounds = true button.layer.borderColor = UIColor.white.cgColor button.layer.borderWidth = 1.0 return button }() private lazy var emailContainerView: UIView = { let view = Utilities.shared.createCustomInputContainerView(withImage: #imageLiteral(resourceName: "ic_mail_outline_white_2x-1"), andTextField: emailTextField) return view }() private lazy var passwordContainerView: UIView = { let view = Utilities.shared.createCustomInputContainerView(withImage: #imageLiteral(resourceName: "ic_lock_outline_white_2x"), andTextField: passwordTextField) return view }() private lazy var fullnameContainerView: UIView = { let view = Utilities.shared.createCustomInputContainerView(withImage: #imageLiteral(resourceName: "ic_person_outline_white_2x"), andTextField: fullnameTextField) return view }() private lazy var usernameContainerView: UIView = { let view = Utilities.shared.createCustomInputContainerView(withImage: #imageLiteral(resourceName: "ic_person_outline_white_2x"), andTextField: usernameTextField) return view }() private let emailTextField: UITextField = { let tf = Utilities.shared.createCustomTextField(withPlaceholder: "Email") tf.tag = 0 return tf }() private let passwordTextField: UITextField = { let tf = Utilities.shared.createCustomTextField(withPlaceholder: "Password") tf.isSecureTextEntry = true tf.tag = 1 return tf }() private let fullnameTextField: UITextField = { let tf = Utilities.shared.createCustomTextField(withPlaceholder: "Full Name") tf.tag = 2 return tf }() private let usernameTextField: UITextField = { let tf = Utilities.shared.createCustomTextField(withPlaceholder: "Username") tf.tag = 3 return tf }() private lazy var alreadyHaveAnAccountButton: UIButton = { let button = Utilities.shared.attributedButton("Already have an account? ", "Log In") button.addTarget(self, action: #selector(handleShowLogIn), for: .touchUpInside) return button }() private let registrationButton: UIButton = { let button = UIButton(type: .system) button.setTitle("Sign Up", for: .normal) button.setTitleColor(.twitterBlue, for: .normal) button.backgroundColor = .white button.setHeight(to: 50.0) button.layer.cornerRadius = 5.0 button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20.0) button.addTarget(self, action: #selector(handleRegistrationButtonClicked), for: .touchUpInside) return button }() private let imagePicker = UIImagePickerController() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() configureUI() } // MARK: - Selectors @objc func handleShowLogIn() { navigationController?.popViewController(animated: true) } @objc func handleAddPhotoButtonClicked() { imagePicker.modalPresentationStyle = .fullScreen present(imagePicker, animated: true, completion: nil) view.endEditing(true) } @objc func handleViewTapped() { print("DEBUG: view tapped") view.endEditing(true) } @objc func handleRegistrationButtonClicked() { print("DEBUG: registration button clicked") } // MARK: - Helpers private func configureUI() { view.backgroundColor = .twitterBlue imagePicker.delegate = self imagePicker.allowsEditing = true view.addSubview(addPhotoButton) addPhotoButton.centerX(inView: view, topAnchor: view.safeAreaLayoutGuide.topAnchor) let stack = UIStackView(arrangedSubviews: [emailContainerView, passwordContainerView, fullnameContainerView, usernameContainerView, registrationButton]) stack.axis = .vertical stack.spacing = 20 stack.distribution = .fillEqually view.addSubview(stack) stack.anchor(top: addPhotoButton.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 32.0, paddingLeft: 32.0, paddingRight: 32.0) view.addSubview(alreadyHaveAnAccountButton) alreadyHaveAnAccountButton.anchor(left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor, paddingLeft: 40.0, paddingRight: 40.0) let tap = UITapGestureRecognizer(target: self, action: #selector(handleViewTapped)) tap.numberOfTapsRequired = 1 view.addGestureRecognizer(tap) } } // MARK: - UIImagePickerControllerDelegate extension RegistrationController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let profileImage = info[.editedImage] as? UIImage else { return } self.addPhotoButton.setImage(profileImage.withRenderingMode(.alwaysOriginal), for: .normal) addPhotoButton.layer.borderWidth = 3.0 dismiss(animated: true, completion: nil) } }
问题原因与解决方案
核心原因
- 使用
.system类型的UIButton,系统默认会给内部图片添加内边距,导致imageView的尺寸小于按钮本身的尺寸,图片被裁切后和按钮的边框之间露出背景色,形成视觉上的双层边框。 - 没有单独设置
imageView的内容填充模式,图片填充逻辑和按钮的裁切逻辑不匹配,进一步放大了间隙。
修复步骤
- 替换按钮类型:将按钮初始化类型从
.system改为.custom,关闭系统自带的默认内边距、高亮样式调整:let button = UIButton(type: .custom) - 修正图片填充逻辑:给
imageView单独设置内容模式和边距,确保图片完全填满按钮的可用区域:button.imageView?.contentMode = .scaleAspectFill button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - 移除冗余设置:可以去掉按钮本身的
contentMode设置,该属性作用于按钮视图而非内部图片,避免逻辑混淆。 - 选择图片后的补充设置:确保设置选中图片为
alwaysOriginal后,按钮的裁切属性保持开启,避免圆角失效:func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let profileImage = info[.editedImage] as? UIImage else { return } self.addPhotoButton.setImage(profileImage.withRenderingMode(.alwaysOriginal), for: .normal) addPhotoButton.layer.borderWidth = 3.0 addPhotoButton.layer.masksToBounds = true // 确保圆角裁切生效 dismiss(animated: true, completion: nil) }
内容的提问来源于stack exchange,提问作者Stephen501
相关产品推荐
相关产品推荐

