如何在UIButton子类中设置UIButton类型

31

我正在对UIButton进行子类化,我想将按钮类型设置为Round Rect。

Button.h

@interface Button : UIButton {}
    - (void)initialize;
@end

Button.m

的翻译是:

Button.m

@implementation Button

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}


-(id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self){
        [self initialize];
    }
    return self;
}

- (void)initialize
{
    self.titleLabel.font = [UIFont systemFontOfSize:20];
    self.titleLabel.textColor = [UIColor redColor];
    self.titleLabel.textAlignment = UITextAlignmentCenter;
   //[UIButton buttonWithType:UIButtonTypeRoundedRect];
}

@end

我尝试了 [UIButton buttonWithType:UIButtonTypeRoundedRect],但它不起作用。有人能建议如何使其起作用吗?

我知道在许多先前的帖子中已经说过不建议子类化 UIButton,但事实上在开发者文档中并没有提到不要子类化它。


2
你真正想做什么?我知道,你说你想设置按钮的类型,但我不相信你。你有一个关于UI的想法,并试图通过子类化UIButton并设置其类型来实现它...但你不会成功。所以告诉我们,你的目标是什么,我相信一定会有不同于你方法的解决方案。 - Kai Huppmann
但你不会的.. :s 嗯,没什么特别的,只是创建子类,以便我可以在整个应用程序中操作按钮..! - Haris Hussain
1
@HarisHussain 为什么你需要一个子类来操作按钮?UIButton接口似乎已经足够了。 - Caleb
为了让应用程序中的按钮保持一致。这样,当我需要更改所有按钮的字体大小时,我不必在使用过的每个地方都进行更改。等等。 - Haris Hussain
那么像 #define BUTTON_FONT [UIFont fontWithName:aFontName size:aFontSize] 这样的常量怎么样? - Kai Huppmann
9个回答

16

UIButton在Swift中的子类

*以下代码适用于Swift 3及以上版本。

根据设计,您无法设置UIButtonbuttonType子类。它会自动设置为custom,这意味着您会得到一个简单的外观和行为作为起点。

如果您想要重用设置按钮外观的代码,您可以在不使用子类的情况下实现。一种方法是提供工厂方法来创建一个UIButton并设置可视属性。

避免使用子类的示例工厂方法

extension UIButton {
    static func createStandardButton() -> UIButton {
        let button = UIButton(type: UIButtonType.system)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        button.setTitleColor(UIColor.black, for: .normal)
        button.setTitleColor(UIColor.gray, for: .highlighted)
        return button
    }
}

let button = UIButton.createStandardButton()

当其他定制技术足够时,我避免对UIButton进行子类化。工厂方法示例是一种选择。还有其他选择,包括UIAppearance API等。

有时候,定制需求需要进行子类化。例如,我创建了UIButton的子类,以对按钮在响应触摸事件时的动画进行精细控制,或者在按钮被点击时调用预定义的委托或闭包(而不是为每个实例分配一个#selector)。

以下是一个基本的UIButton子类示例作为起点。

UIButton子类示例

internal class CustomButton: UIButton {

    init() {
        // The frame can be set outside of the initializer. Default to zero.
        super.init(frame: CGRect.zero)
        initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        // Called when instantiating from storyboard or nib
        super.init(coder: aDecoder)
        initialize()
    }

    func initialize() {
        print("Execute common initialization code")
    }
}

let button = CustomButton()
print(button.buttonType == .custom)  // true

关于UIButtonType的说明

自从2012年提出这个问题以来,UIButtonType.roundedRect已经被弃用。头文件注释建议使用UIButtonType.system代替。

以下是从UIButton.h在Xcode中转换为Swift的内容。

public enum UIButtonType : Int {

    case custom // no button type

    @available(iOS 7.0, *)
    case system // standard system button

    case detailDisclosure
    case infoLight
    case infoDark
    case contactAdd

    public static var roundedRect: UIButtonType { get } // Deprecated, use UIButtonTypeSystem instead
}

这里是设置所谓的“样式”设置的关键信息,可以在故事板下拉菜单中找到。

enter image description here

https://stackoverflow.com/a/72913854/294884


2
虽然我不是故事板的粉丝,但工厂方法并不适用于故事板。除此之外,这是一个非常好的策略。 - solidcell
这是一个很好的老答案。事实上,你几乎总是最好不要继承UIButton。@MobileDan,我冒昧地在最后添加了一些关键的新信息供您参考。谢谢。 - undefined

13
您可能会发现 CocoaBuilder 的讨论线程 如何子类化 UIButton? 很有帮助,特别是 Jack Nutting 建议忽略 buttonType

请注意,这种方式并没有将 buttonType 显式地设置为任何值, 这可能意味着它是 UIButtonTypeCustom。文档似乎并没有实际指定这一点,但由于这是枚举中的 0 值,因此很可能会发生这种情况(这似乎也是可观察到的行为)


12

虽然不完全符合您的需求,但请记住您的子类仍然具有buttonWithType方法,并且它可以正常工作。

buttonWithType会调用您子类的initWithFrame方法,并适当地设置类型。

SubclassButton *myButton=[SubclassButton buttonWithType:UIButtonTypeRoundedRect];

你救了我的一天! - alcamla
问题在于这是一个便利初始化方法,你既不能重写它,也不能在 self 之前调用超类的便利初始化方法。 - Amber K
1
@AmberK,你需要覆盖的任何内容都应该在initWithFrame中完成。SubclassButton.init(type: .roundedRect)返回一个类型为SubclassButton的按钮。 - Confused Vorlon
好的,所以按钮类型的超级便利初始化将调用我的重写指定初始化(frame :)。谢谢,现在我明白了 :) - Amber K

6

我创建了一个方便的初始化方法,这对我很有用:

class CustomButton: UIButton {
    
    convenience init() {
        self.init(type: .system)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        /*Do customization here*/
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        /*Do customization here*/
    }
}

1
太棒了!对于阅读的人来说,请注意,你不需要第二和第三个从句,只有在进行进一步的自定义时才需要。只需添加方便的 init() { self.init(type: .system) } 就可以搞定了。点击这里 https://stackoverflow.com/a/72913854/294884 了解如何设置同样奇怪的所谓“样式”值。 - undefined

3
由于最初的意图是在初始化点配置按钮,您可以声明一个辅助初始化器并传递配置,如下所示:
extension UIButton {
    convenience init(type: ButtonType, configuration(UIButton) -> ()) {
        self.init(type: type)
        configuration(self)
    }
}

这将使用给定的类型初始化一个按钮,并在返回新初始化的按钮之前对其进行任何配置。

使用方法:
let myButton = UIButton(type: .contactAdd) {
    $0.tintColor = .red
}

为所有按钮创建一致的样式?

您可以创建一个配置函数,并将其应用于所有按钮:

func consistentConfiguration(_ button: UIButton) -> () {
    button.tintColor = .red
}

let myConsistentButton1 = UIButton(type: .contactAdd, configuration: consistentConfiguration)
let myConsistentButton2 = UIButton(type: .detailDisclosure, configuration: consistentConfiguration)

⚠️你可以将此配置转换为另一个辅助初始化程序,但不建议这样做,因为它没有正确的范围。

2
哇 - 真是优雅的东西 - undefined
1
@Motjaba,我觉得这个赏金让你突破了10万的大关!干得漂亮! - undefined

1
使用iOS7,这比以前更加相关,因为有时您需要使用UIButtonTypeSystem,您不能简单地重写init并执行[MyButton alloc] init],因为它是一个UIButtonTypeCustom,不反映tintColor也没有良好的高亮淡出效果。
要子类化,请像这样创建一个静态构造函数:
+ (instancetype)myButtonWithTitle:(NSString *)title imageNamed:(NSString *)imageName target:(id)target action:(SEL)action {
    MyButton *button = [self buttonWithType:UIButtonTypeSystem];
    ... my custom setup for title, image, target, etc ...
    return button;
}

0
你绝对可以创建一个 UIButton 的子类。我成功地创建了一个 DateButton,它看起来像一个 UITextField,但当用户触摸它时,它会在 Popover 中显示一个 DatePicker。它还有一个属性用于存储日期等信息。如果上述方法无法解决您的问题,请告诉我,我将发布更多详细信息。

0
这不是一个好的解决方案,所以我们需要“感谢”苹果使用这个支撑。XD
//if self.buttonType == .system && self.state.contains(.highlighted) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        self.titleLabel?.alpha = 1.0
    }
//}

例如,在touchesBegan(...)中添加。

-2

这个怎么样?

- (id)initWithFrame:(CGRect)frame 
{
    self = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
    if (self) {
        self.frame = frame;
    }

    return self;
}

7
这将给你一个UIButton实例,而不是子类的实例。因此,例如,子类添加的任何额外的ivars都将不存在。 - Caleb
[UIRoundedRectButton initialize]: unrecognized selector sent to instance 0x8a406a0 - Haris Hussain
它可以在initWithFrame上工作,但如何在InitWithCoder上工作呢?否则请保留它,否则会泄漏~~ - adali
这个也不行...我已经尝试了很多次:P - Haris Hussain
1
@HarisHussain +initialize 是一个类方法,而不是实例方法。请参考Objective-C: init vs. initialize了解其解释。使用同名的实例方法可能会起作用,但也可能是问题的一部分。 - Caleb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接