You need to enable JavaScript to run this app. Xcode项目中自定义UIButton为何出现畸变并显示两层重叠边框?
优惠活动
大模型
产品
解决方案
定价
更多

Xcode项目中自定义UIButton为何出现畸变并显示两层重叠边框?

自定义圆形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 的内容填充模式,图片填充逻辑和按钮的裁切逻辑不匹配,进一步放大了间隙。

修复步骤

  1. 替换按钮类型:将按钮初始化类型从 .system 改为 .custom,关闭系统自带的默认内边距、高亮样式调整:
    let button = UIButton(type: .custom)
    
  2. 修正图片填充逻辑:给 imageView 单独设置内容模式和边距,确保图片完全填满按钮的可用区域:
    button.imageView?.contentMode = .scaleAspectFill
    button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    
  3. 移除冗余设置:可以去掉按钮本身的 contentMode 设置,该属性作用于按钮视图而非内部图片,避免逻辑混淆。
  4. 选择图片后的补充设置:确保设置选中图片为 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 21:15:09