Comment puis-je faire en sorte qu'un bouton ait une bordure arrondie dans Swift?

232

Je crée une application à l'aide de swift dans la dernière version de Xcode 6, et j'aimerais savoir comment je peux modifier mon bouton afin qu'il puisse avoir une bordure arrondie que je pourrais ajuster moi-même si nécessaire. Une fois cela fait, comment puis-je changer la couleur de la bordure elle-même sans y ajouter d'arrière-plan? En d'autres termes, je veux un bouton légèrement arrondi sans arrière-plan, seulement une bordure de 1 pt d'une certaine couleur.

GuiGui23
la source

Réponses:

528

Utilisez button.layer.cornerRadius, button.layer.borderColoret button.layer.borderWidth. Notez que borderColornécessite un CGColor, vous pouvez donc dire (Swift 3/4):

button.backgroundColor = .clear
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
retourner vrai
la source
2
J'ai essayé cela, mais j'ai un petit problème, le premier texte commence à partir de la frontière elle-même, donc ce n'est pas si beau qu'il existe un moyen de résoudre ce problème
vinbhai4u
2
@ vinbhai4u Essayez d'utiliser titleEdgeInsets.
retour vrai
comment pouvons-nous rendre la boîte plus grande que le texte du bouton?
Plus tôt
créer une sortie pour le bouton pour s'y référer
Jobs
je veux l'utiliser comme ça mais ne fonctionne pas :( UIButton.appearance (). layer.cornerRadius = 4
MR
65

Pour faire ce travail dans le storyboard (Interface Builder Inspector)

Avec l'aide de IBDesignable, nous pouvons ajouter plus d'options à Interface Builder Inspector UIButtonet les modifier sur le storyboard. Tout d'abord, ajoutez le code suivant à votre projet.

@IBDesignable extension UIButton {

    @IBInspectable var borderWidth: CGFloat {
        set {
            layer.borderWidth = newValue
        }
        get {
            return layer.borderWidth
        }
    }

    @IBInspectable var cornerRadius: CGFloat {
        set {
            layer.cornerRadius = newValue
        }
        get {
            return layer.cornerRadius
        }
    }

    @IBInspectable var borderColor: UIColor? {
        set {
            guard let uiColor = newValue else { return }
            layer.borderColor = uiColor.cgColor
        }
        get {
            guard let color = layer.borderColor else { return nil }
            return UIColor(cgColor: color)
        }
    }
}

Ensuite, définissez simplement les attributs des boutons sur le storyboard.

entrez la description de l'image ici

Fangming
la source
4
Ceci est plus ou moins une copie de la réponse ci-dessous de Hamza Ghazouani.
retour vrai
lorsque j'utilise la solution ci-dessus pour le boarderColor, sa construction a échoué dans interfacebuilder, j'utilise le code ci-dessous, mais je réussis si j'ai utilisé le didset plutôt que de définir et d'obtenir. @IBInspectable var borderColor: UIColor = .red {// didSet {// layer.borderColor = borderColor.cgColor //} set {guard let uiColor = newValue else {return} layer.borderColor = uiColor.cgColor} get {guard let color = layer.borderColor else {return nil} return UIColor (cgColor: color)}}
Amr Angry
1
Wow, comme par magie! Je vous remercie! Googlers - assurez-vous de mettre cet extrait de code complètement en dehors de votre dernière accolade dans le fichier dans lequel vous le placez - l'extrait de code ne devrait pas être dans une classe ou une fonction
velkoon
24

J'ai créé une sous-classe UIButton simple qui utilise le tintColorpour ses couleurs de texte et de bordure et lorsqu'elle est mise en surbrillance, change son arrière-plan en tintColor.

class BorderedButton: UIButton {

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    layer.borderWidth = 1.0
    layer.borderColor = tintColor.CGColor
    layer.cornerRadius = 5.0
    clipsToBounds = true
    contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
    setTitleColor(tintColor, forState: .Normal)
    setTitleColor(UIColor.whiteColor(), forState: .Highlighted)
    setBackgroundImage(UIImage(color: tintColor), forState: .Highlighted)
}
}

Cela utilise une extension UIImage qui crée une image à partir d'une couleur, j'ai trouvé ce code ici: https://stackoverflow.com/a/33675160

Il fonctionne mieux lorsqu'il est défini sur le type Personnalisé dans le générateur d'interface, car le type de système par défaut modifie légèrement les couleurs lorsque le bouton est mis en surbrillance.

sgib
la source
13

Cette classe est basée sur tous les commentaires et suggestions dans les réponses, et peut également être conçue directement à partir de xcode. Copiez dans votre projet et insérez n'importe quel bouton UIB et modifiez pour utiliser une classe personnalisée, il vous suffit maintenant de sélectionner la couleur de la bordure ou de l'arrière-plan de xcode pour les états normaux et / ou en surbrillance.

//
//  RoundedButton.swift
//

import UIKit

@IBDesignable
class RoundedButton:UIButton {

    @IBInspectable var borderWidth: CGFloat = 0 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }
    //Normal state bg and border
    @IBInspectable var normalBorderColor: UIColor? {
        didSet {
            layer.borderColor = normalBorderColor?.CGColor
        }
    }

    @IBInspectable var normalBackgroundColor: UIColor? {
        didSet {
            setBgColorForState(normalBackgroundColor, forState: .Normal)
        }
    }


    //Highlighted state bg and border
    @IBInspectable var highlightedBorderColor: UIColor?

    @IBInspectable var highlightedBackgroundColor: UIColor? {
        didSet {
            setBgColorForState(highlightedBackgroundColor, forState: .Highlighted)
        }
    }


    private func setBgColorForState(color: UIColor?, forState: UIControlState){
        if color != nil {
            setBackgroundImage(UIImage.imageWithColor(color!), forState: forState)

        } else {
            setBackgroundImage(nil, forState: forState)
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        layer.cornerRadius = layer.frame.height / 2
        clipsToBounds = true

        if borderWidth > 0 {
            if state == .Normal && !CGColorEqualToColor(layer.borderColor, normalBorderColor?.CGColor) {
                layer.borderColor = normalBorderColor?.CGColor
            } else if state == .Highlighted && highlightedBorderColor != nil{
                layer.borderColor = highlightedBorderColor!.CGColor
            }
        }
    }

}

//Extension Required by RoundedButton to create UIImage from UIColor
extension UIImage {
    class func imageWithColor(color: UIColor) -> UIImage {
        let rect: CGRect = CGRectMake(0, 0, 1, 1)
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), false, 1.0)
        color.setFill()
        UIRectFill(rect)
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}
le0diaz
la source
1
Pourquoi importer Foundationquand importer UIKitsuffit vraiment?
retour vrai
@returntrue Oui, vous avez raison, la Fondation n'est pas requise, je viens du modèle de base xcode d'origine si je ne me trompe pas. J'ai mis à jour le code.
le0diaz
La seule chose est que cela rendra le bouton circulaire tout le temps. Vous pouvez peut-être également ajouter une propriété de rayon d'angle pour résoudre ce problème.
Andreas777
10

Sur la base de la réponse @returntrue, j'ai réussi à l'implémenter dans Interface Builder.

Pour obtenir le bouton des coins arrondis à l'aide d'Interface Builder, ajoutez une clé Path = "layer.cornerRadius"avec Type = "Number"et Value = "10"(ou toute autre valeur requise) dans le " User Defined RunTime Attribute" du Identity Inspectorbouton.

Zvi
la source
1
Cela fonctionne bien, mais n'est pas spécifique à Swift. La question demande explicitement comment obtenir l'effet souhaité en utilisant Swift, pas en utilisant un storyboard ou similaire.
retour vrai
2
Ma réponse ne s'adressait pas au PO mais à d'autres lecteurs confrontés à mon problème. et seulement en trouvant ce poste ont pu résoudre le problème. À mon avis et selon mon expérience, les réponses ne doivent pas être exactement à ce que le PO a demandé, tant qu'elles y sont liées et donnent des informations supplémentaires utiles aux lecteurs. En votant contre ces réponses, vous découragez les réponses utiles.
Zvi
1
Bien que cela soit généralement vrai - en particulier pour les questions générales - cette question est claire dans son caractère. Il existe une multitude de questions qui demandent une solution explicite via le storyboard, et si possible les lecteurs voulaient avoir une solution qui utilise le storyboard, puis ajouter "storyboard" aux termes de recherche google suffit pour afficher ces questions. Je ne dis pas que votre réponse est mauvaise, mais plutôt au mauvais endroit.
retour vrai
Comme j'ai trouvé que ce message était le plus proche de résoudre mon problème, je l'ai posté ici. Suivre votre chemin signifie que j'aurais dû écrire un nouveau message et répondre à mon message, ce qui me semble un peu exagéré.
Zvi
1
J'ai trouvé ces questions qui semblent résoudre votre problème mieux que celui-ci: stackoverflow.com/questions/12301256 ; stackoverflow.com/questions/20477990
retour vrai
6

Vous pouvez utiliser cette sous-classe d'UIButton pour personnaliser UIButton selon vos besoins.

visitez ce repo github pour référence

class RoundedRectButton: UIButton {

    var selectedState: Bool = false

    override func awakeFromNib() {
        super.awakeFromNib()
        layer.borderWidth = 2 / UIScreen.main.nativeScale
        layer.borderColor = UIColor.white.cgColor
        contentEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
    }


    override func layoutSubviews(){
        super.layoutSubviews()
        layer.cornerRadius = frame.height / 2
        backgroundColor = selectedState ? UIColor.white : UIColor.clear
        self.titleLabel?.textColor = selectedState ? UIColor.green : UIColor.white
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        selectedState = !selectedState
        self.layoutSubviews()
    }
}
Nikesh Jha
la source
celui qui a voté contre, avez-vous même essayé de travailler de cette façon ??? ne perdez pas votre temps à voter
Nikesh Jha
Pourquoi un downvote? cela semble fonctionner et est très propre.
ColdGrub1384
@ ColdGrub1384 exactement :)
Nikesh Jha
3

Je pense que la manière la plus simple et la plus propre est d'utiliser le protocole pour éviter l'héritage et la répétition du code. Vous pouvez modifier ces propriétés directement à partir du storyboard

protocol Traceable {
    var cornerRadius: CGFloat { get set }
    var borderColor: UIColor? { get set }
    var borderWidth: CGFloat { get set }
}

extension UIView: Traceable {

    @IBInspectable var cornerRadius: CGFloat {
        get { return layer.cornerRadius }
        set {
            layer.masksToBounds = true
            layer.cornerRadius = newValue
        }
    }

    @IBInspectable var borderColor: UIColor? {
        get {
            guard let cgColor = layer.borderColor else { return nil }
            return  UIColor(cgColor: cgColor)
        }
        set { layer.borderColor = newValue?.cgColor }
    }

    @IBInspectable var borderWidth: CGFloat {
        get { return layer.borderWidth }
        set { layer.borderWidth = newValue }
    }
}

Mettre à jour

Dans ce lien, vous pouvez trouver un exemple avec l'utilité du protocole Traceable

entrez la description de l'image ici

Hamza
la source
2
Vous n'avez pas vraiment besoin du protocole ici. Fonctionne parfaitement sans lui.
retour vrai
2
Bonjour @returntrue, je ne sais pas pourquoi vous dévaluez ma réponse ... Je pense que vous ne l'avez pas comprise, je vous invite à regarder cette session: developer.apple.com/videos/play/wwdc2015/408 Pourquoi utiliser le protocole? le protocole permet plus de flexibilité, je peux avoir une implémentation par défaut, etc. Ma réponse permet d'éditer ces propriétés dans le storyboard, et je n'ai pas besoin de répéter le code ou de sous-classer n'importe quelle classe Votre réponse est correcte mais ici j'ai partagé une autre façon de faire Vous dévalorisez toutes les autres réponses, vous devez vous rappeler que Stackoverflow sert à partager nos connaissances avant de gagner en réputation
Hamza
2
Exact, votre code autorise toutes les choses que vous avez répertoriées. MAIS, comme UIView est la superclasse de tous les éléments de l'interface utilisateur, votre extension UIView suffit pour faire le travail. Le protocole Traceable n'est en aucun cas requis et n'apporte aucune amélioration (car il se traduit simplement par UIView - seuls les UIViews et ses sous-classes sont traçables).
retour vrai
1
salut @returntrue, vous avez raison dans ce cas d'utilisation, peut-être, nous n'avons pas besoin de protocole, mais avec lui, nous avons plus de flexibilité, et par exemple, nous pouvons ajouter des valeurs par défaut à appliquer et disponibles uniquement pour UIImageView et UIButton, I mettra à jour ma réponse avec cet exemple :)
Hamza
1
Cela n'a pas vraiment de sens d'appliquer des valeurs par défaut pour des propriétés comme la couleur de la bordure du rayon d'angle. Ces valeurs doivent être appliquées à chaque instance de vue dans votre interface utilisateur séparément de manière significative.
retour vrai
3
   @IBOutlet weak var yourButton: UIButton! {
        didSet{
            yourButton.backgroundColor = .clear
            yourButton.layer.cornerRadius = 10
            yourButton.layer.borderWidth = 2
            yourButton.layer.borderColor = UIColor.white.cgColor
        }
    }
Giang
la source
1

comme astuce de côté, assurez-vous que votre bouton n'est pas une sous-classe d'une classe personnalisée dans le story-board, dans un tel cas, votre meilleur emplacement de code devrait être dans une classe personnalisée, le code à lui seul ne fonctionne que hors de la classe personnalisée si votre bouton est une sous-classe par défaut Classe UIButton et sortie de celui-ci, j'espère que cela peut aider quelqu'un à se demander pourquoi les radios d'angle ne s'appliquent pas à mon bouton à partir du code.

IsPha
la source
0
@IBOutlet weak var button: UIButton!

...

Je pense que pour le rayon juste assez ce paramètre:

button.layer.cornerRadius = 5
Iryna Batvina
la source
Cela n'ajoute rien de nouveau, semble-t-il.
retour vrai
0

ESSAYEZ CECI Bordure de bouton avec coins arrondis

anyButton.backgroundColor = .clear

anyButton.layer.cornerRadius = anyButton.frame.height / 2

anyButton.layer.borderWidth = 1

anyButton.layer.borderColor = UIColor.black.cgColor
Raghib Arshi
la source
oui, mais il introduit l'idée d'utiliser un cornerRadius qui est dynamique, je ne pense pas qu'il a été simplement copié-collé.
benc
0
import UIKit

@IBDesignable
class RoundedButton: UIButton {
    
    @IBInspectable var cornerRadius: CGFloat = 8
    @IBInspectable var borderColor: UIColor? = .lightGray
    
    override func draw(_ rect: CGRect) {
        layer.cornerRadius = cornerRadius
        layer.masksToBounds = true
        layer.borderWidth = 1
        layer.borderColor = borderColor?.cgColor
        
    }
    
    
}
Azade Rahmati
la source
-1

Vous pouvez sous UIButton- classer et y ajouter des @IBInspectablevariables afin de pouvoir configurer les paramètres des boutons personnalisés via le StoryBoard "Attribute Inspector". Ci-dessous, j'écris ce code.

@IBDesignable
class BHButton: UIButton {

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

    @IBInspectable lazy var isRoundRectButton : Bool = false

    @IBInspectable public var cornerRadius : CGFloat = 0.0 {
        didSet{
            setUpView()
        }
    }

    @IBInspectable public var borderColor : UIColor = UIColor.clear {
        didSet {
            self.layer.borderColor = borderColor.cgColor
        }
    }

    @IBInspectable public var borderWidth : CGFloat = 0.0 {
        didSet {
            self.layer.borderWidth = borderWidth
        }
    }

    //  MARK:   Awake From Nib

    override func awakeFromNib() {
        super.awakeFromNib()
        setUpView()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setUpView()
    }

    func setUpView() {
        if isRoundRectButton {
            self.layer.cornerRadius = self.bounds.height/2;
            self.clipsToBounds = true
        }
        else{
            self.layer.cornerRadius = self.cornerRadius;
            self.clipsToBounds = true
        }
    }

}
Bhavesh Patel
la source
-1

Je pense que c'est la forme simple

        Button1.layer.cornerRadius = 10(Half of the length and width)
        Button1.layer.borderWidth = 2
Graycodder
la source