简体   繁体   English

SwiftUI:在初始化程序中没有标签/字符串但具有 ButtonStyle 的按钮

[英]SwiftUI: Button without Label/String in the initializer but with ButtonStyle

SwiftUI has a few Button initializers, but all of them require either a String or some View as the parameter alongside with the action . SwiftUI 有一些Button初始化器,但它们都需要一个Stringsome View作为参数以及action

However, the button's appearance can also be customized with the help of ButtonStyle s which can add custom views to it.但是,按钮的外观也可以在ButtonStyle的帮助下进行自定义,它可以添加自定义视图。

Let's consider a Copy button with the following icon:让我们考虑一个带有以下图标的复制按钮:

SF 符号复制

The style I've made for the button looks as follows:我为按钮制作的样式如下所示:

struct CopyButtonStyle: ButtonStyle {

    init() {}

    func makeBody(configuration: Configuration) -> some View {
        let copyIconSize: CGFloat = 24
        return Image(systemName: "doc.on.doc")
            .renderingMode(.template)
            .resizable()
            .frame(width: copyIconSize, height: copyIconSize)
            .accessibilityIdentifier("copy_button")
            .opacity(configuration.isPressed ? 0.5 : 1)
    }
}

It works perfectly, however, I have to initialize the Button with an empty string at call site:它工作得很好,但是,我必须在调用站点用一个空字符串初始化Button

Button("") {
    print("copy")
}
.buttonStyle(CopyButtonStyle())

So, the question is how can I get rid of the empty string in the button's initialization parameter?那么,问题是如何摆脱按钮初始化参数中的空字符串?

Potential Solution潜在解决方案

I was able to create a simple extension that accomplishes the job I need:我能够创建一个简单的扩展来完成我需要的工作:

import SwiftUI

extension Button where Label == Text {
    init(_ action: @escaping () -> Void) {
        self.init("", action: action)
    }
}

Call site:调用站点:

Button() { // Note: no initializer parameter
    print("copy")
}
.buttonStyle(CopyButtonStyle())

But curious, whether I'm using the Button struct incorrectly and there is already a use-case for that, so that I can get rid of this extension.但是很好奇,我是否错误地使用了Button结构并且已经有一个用例,这样我就可以摆脱这个扩展。

An easier way than making a ButtonStyle configuration is to pass in the label directly:比进行ButtonStyle配置更简单的方法是直接传入 label:

Button {
    print("copy")
} label: {
    Label("Copy", systemImage: "doc.on.doc")
        .labelStyle(.iconOnly)
}

This also comes with some benefits:这也带来了一些好处:

  1. By default, the button is blue to indicate it can be tapped默认情况下,按钮为蓝色,表示可以点击
  2. No weird stretching of the image that you currently have没有对您当前拥有的图像进行奇怪的拉伸
  3. No need to implement how the opacity changes when pressed无需实现按下时不透明度如何变化

You could also refactor this into its own view:您还可以将其重构为自己的视图:

struct CopyButton: View {
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            Label("Copy", systemImage: "doc.on.doc")
                .labelStyle(.iconOnly)
        }
    }
}

Called like so:像这样调用:

CopyButton {
    print("copy")
}

Which looks much cleaner overall.整体看起来更干净。

Here is a right way for what you are trying to do, you do not need make a new ButtonStyle for each kind of Button, you can create just one and reuse it for any other Buttons you want.这是您尝试做的正确方法,您不需要为每种按钮创建一个新的 ButtonStyle,您可以只创建一个并将其重用于您想要的任何其他按钮。 Also I solved your Image stretching issue with .scaledToFit() .我还用.scaledToFit()解决了您的图像拉伸问题。

struct CustomButtonView: View {
    
    let imageString: String
    let size: CGFloat
    let identifier: String
    let action: (() -> Void)?
    
    init(imageString: String, size: CGFloat = 24.0, identifier: String = String(), action: (() -> Void)? = nil) {
        self.imageString = imageString
        self.size = size
        self.identifier = identifier
        self.action = action
    }

    var body: some View {
        
        return Button(action: { action?() } , label: {
            
            Image(systemName: imageString)
               .renderingMode(.template)
               .resizable()
               .scaledToFit()
               .frame(width: size, height: size)
               .accessibilityIdentifier(identifier)
            
        })
        .buttonStyle(CustomButtonStyle())

    }
    
}

struct CustomButtonStyle: ButtonStyle {

    func makeBody(configuration: Configuration) -> some View {
        return configuration.label
            .opacity(configuration.isPressed ? 0.5 : 1.0)
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
    }
}

use case:用例:

struct ContentView: View {
    var body: some View {
        
        CustomButtonView(imageString: "doc.on.doc", identifier: "copy_button", action: { print("copy") })

    }
}

You can use EmptyView for label, like您可以将EmptyView用于 label,例如

    Button(action: { // Note: no initializer parameter
         print("copy")
    }, label: { EmptyView() })
    .buttonStyle(CopyButtonStyle())

but wrapping it in custom button type (like shown in other answer) is more preferable from re-use and code readability point of view.但是从重用和代码可读性的角度来看,将其包装在自定义按钮类型中(如其他答案所示)更可取。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM