The Util Designer
SwiftUI 在Menu中使用客置化MenuStyle
xcode 13.4.1, swift 5.5, iOS 15.4
2022-08-29
SwiftUI使用Menu基本使用 知道Menu的基本用法,現在來看看如何使用MenuStyle來改變Menu的外觀。
1. 先創建一個基本的Menu。
import SwiftUI

struct MenuExample : View {
    var body: some View {
        
        Menu {
            Button {
                
            } label: {
                Label("Share", systemImage: "square.and.arrow.up")
            }
            
            Button {
                
            } label: {
                Label("Print", systemImage: "printer")
            }
            
        } label: {
            Text("Menu Exammple")
        }

    }
}
2. 以上的Menu只用了一個Text,有時要配合App的設計,需要改變Menu的外觀,比如藍色圓角長方形,實現如下:
import SwiftUI

struct MenuExample : View {
    var body: some View {
        
        Menu {
            Button {
                
            } label: {
                Label("Share", systemImage: "square.and.arrow.up")
            }
            
            Button {
                
            } label: {
                Label("Print", systemImage: "printer")
            }
            
        } label: {
            Text("Menu Exammple")
                .padding()
                .foregroundColor(.white)
                .background(.blue, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
        }

    }
}
3. 設置Menu的外觀,除了可以像以上直接改變Menu的Label View,另一種方法是創建MenuStyle的子類,下面就是用這種方法實現了藍色圓角長方形,並使用menuStyle指定該子類。
import SwiftUI

struct MenuExample : View {
    var body: some View {
        
        Menu {
            Button {
                
            } label: {
                Label("Share", systemImage: "square.and.arrow.up")
            }
            
            Button {
                
            } label: {
                Label("Print", systemImage: "printer")
            }
            
        } label: {
            Text("Menu Exammple")
        }
        .menuStyle(ColorMenuButtonStyle())
    }
}

struct ColorMenuButtonStyle : MenuStyle {
    func makeBody(configuration: Configuration) -> some View {
        Menu(configuration)
            .padding()
            .foregroundColor(.white)
            .background(.blue, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
    }
}
4. 進一步,我們在ColorMenuButtonStyle增加一個顏色的設置,使得有更多的變化及靈活性。
import SwiftUI

struct MenuExample : View {
    var body: some View {
        
        Menu {
            Button {
                
            } label: {
                Label("Share", systemImage: "square.and.arrow.up")
            }
            
            Button {
                
            } label: {
                Label("Print", systemImage: "printer")
            }
            
        } label: {
            Text("Menu Exammple")
        }
        .menuStyle(ColorMenuButtonStyle(color: .mint))
    }
}

struct ColorMenuButtonStyle : MenuStyle {
    let color : Color
    func makeBody(configuration: Configuration) -> some View {
        Menu(configuration)
            .padding()
            .foregroundColor(.white)
            .background(color, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
    }
}
5. 以下使用一個例子來演示可以如何重用ColorMenuButtonStyle,這個例子是選擇Menu的item來決定Menu使用哪個ColorMenuButtonStyle來套用到其menuStyle中,為了不要搞得太複雜,也為了演示如何擴展MenuStyle,我們以下只寫了4種顏色的ColorMenuButtonStyle並將其放到MenuStyle,下面程式用colorMenuButtonStyle變量來記住選擇了那一個ColorMenuButtonStyle,再用Menu的menuStyle方法來套用到Menu中,實作如下:
import SwiftUI

struct MenuExample : View {
    @State var colorMenuButtonStyle : ColorMenuButtonStyle = .redColorMenuButtonStyle
    let colorMenuButtonStyles : [ ColorMenuButtonStyle ] = [
        .redColorMenuButtonStyle, .greenColorMenuButtonStyle,
        .blueColorMenuButtonStyle, .mintColorMenuButtonStyle
    ]
    var body: some View {
        
        Menu {
            
            ForEach(colorMenuButtonStyles, id : \.self) { menuButtonStyle in
                Button {
                    colorMenuButtonStyle = menuButtonStyle
                } label: {
                    if menuButtonStyle == colorMenuButtonStyle {
                        Label(menuButtonStyle.color.description, systemImage: "checkmark")
                    } else {
                        Text(menuButtonStyle.color.description)
                    }
                }
            }
            
            
        } label: {
            Text("Menu Exammple")
        }
        .menuStyle(colorMenuButtonStyle)
    }
}

struct ColorMenuButtonStyle : MenuStyle, Hashable {
    let color : Color
    func makeBody(configuration: Configuration) -> some View {
        Menu(configuration)
            .padding()
            .foregroundColor(.white)
            .font(.system(size: 28, weight: .bold, design: .rounded))
            .background(color, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
    }
}

extension MenuStyle where Self == ColorMenuButtonStyle {
    static var redColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .red) }
    static var greenColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .green) }
    static var blueColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .blue) }
    static var mintColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .mint) }
}
6. 我們還可以用SwiftUI提供的animation來控制切換MenuStyle的速度,實現如下:
import SwiftUI

struct MenuExample : View {
    @State var colorMenuButtonStyle : ColorMenuButtonStyle = .redColorMenuButtonStyle
    let colorMenuButtonStyles : [ ColorMenuButtonStyle ] = [
        .redColorMenuButtonStyle, .greenColorMenuButtonStyle,
        .blueColorMenuButtonStyle, .mintColorMenuButtonStyle
    ]
    var body: some View {
        
        Menu {
            
            ForEach(colorMenuButtonStyles, id : \.self) { menuButtonStyle in
                Button {
                    withAnimation(.easeIn(duration: 1)) {
                        colorMenuButtonStyle = menuButtonStyle
                    }
                } label: {
                    if menuButtonStyle == colorMenuButtonStyle {
                        Label(menuButtonStyle.color.description, systemImage: "checkmark")
                    } else {
                        Text(menuButtonStyle.color.description)
                    }
                }
            }
            
            
        } label: {
            Text("Menu Exammple")
        }
        .menuStyle(colorMenuButtonStyle)
    }
}

struct ColorMenuButtonStyle : MenuStyle, Hashable {
    let color : Color
    func makeBody(configuration: Configuration) -> some View {
        Menu(configuration)
            .padding()
            .foregroundColor(.white)
            .font(.system(size: 28, weight: .bold, design: .rounded))
            .background(color, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
    }
}

extension MenuStyle where Self == ColorMenuButtonStyle {
    static var redColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .red) }
    static var greenColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .green) }
    static var blueColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .blue) }
    static var mintColorMenuButtonStyle : ColorMenuButtonStyle { .init(color: .mint) }
}