SwiftUI更换应用图标
SwiftUI更换应用图标

SwiftUI更换应用图标

在 SwiftUI 中,可以使用 UIApplication 提供的接口来更换应用图标。

需要注意的是,这个方法适用于iOS 15.0+,可能不适应于之前的iOS版本。

1、上传应用备选图标

在Xcode中找到Assets资源文件夹,点击底部新增按钮,找到iOS – iOS App icon。

点击后,会自动创建一个图标占位符。将图标重新命名,并上传备选图标。

默认的主图标通常命名为 AppIcon,替代图标可以添加在 AppIcon 下,命名为希望使用的图标名称(如 AppIcon0、AppIcon1 等)。

建议将上传的图标放在一个单独的文件夹中,避免与其他图片混淆,因为图标和其他图片不同,图标不可以用Image直接访问,需要UIImage访问。

我在这里创建了AppIcon0、AppIcon1…AppIcon4,共五个备选图标。

2、配置Build Settings

在Xcode文件中,点击左上角的项目图标,找到TARGETS – Build Settings,在输入框中搜索“icon”。

找到“Alternate App Icon Sets字段”,在可编辑的一栏中双击打开,并输入可更换的应用图标。

输入的名称和上传的应用备选图标名称一致。

3、SwiftUI更换图标

在SwiftUI文件中,首先创建一个方法:

// 更换图标方法
func setAlternateIconNameFunc(name: String) {
    UIApplication.shared.setAlternateIconName(name == "AppIcon" ? nil : name)
}

这个setAlternateIconName方法可以用于更换应用图标。

var appIcon: [Int] {
    return Array(isInAppPurchase ? 0..<5 : 0..<2)
}
        
ForEach(appIcon, id: \.self) { index in
    Button(action: {
        setAlternateIconNameFunc(name: "AppIcon\(index)")
    }, label: {
        Rectangle()
            .foregroundColor(.white)
            .frame(width: isPadScreen ? 180 : 100,height: isPadScreen ? 180 : 100)
            .cornerRadius(10)
            .clipped()
            .overlay {
              Image(uiImage: UIImage(named: "AppIcon\(index)") ?? UIImage())
                    .resizable()
                    .scaledToFill()
                    .frame(width: isPadScreen ? 175 : 95,height: isPadScreen ? 175 : 95)
                    .cornerRadius(10)
                    .clipped()
            }
    })
}

在视图中使用ForEach循环遍历,我这里设置了一个appIcon数组,如果是内购用户,显示五个图标,如果普通用户,则显示两个图标,可以根据自身需求设定范围。

ForEach遍历这个appIcon数组,在ForEach中放入一个Button按钮,内容是一个简单的背景和图标的嵌套,实际需要了解的内容是,因为图标和普通照片不一样,不能直接使用Image(:_)获取,而是需要使用UIImage获取图标。

Image(uiImage: UIImage(named: "AppIcon\(index)") ?? UIImage())

这里通过ForEach获取index,并显示图标到屏幕上。

当点击按钮时,会执行setAlternateIconNameFunc方法:

setAlternateIconNameFunc(name: "AppIcon\(index)")

在这个方法中,如果传入的name是AppIcon,将setAlternateIconName设置为nil,表示使用应用的原始图标。如果传入其他name,则使用对应的名称。

// 更换图标方法
func setAlternateIconNameFunc(name: String) {
    UIApplication.shared.setAlternateIconName(name == "AppIcon" ? nil : name)
}

这里传入的name,就与我们前面提到的 Build Settings 中配置的内容一致。

如果Build Settings配置错了,或者name传入错了,图标是无法配置成功的。

4、实现效果

在实现效果中,可以看到当点击图标后,会执行setAlternateIconNameFunc方法,系统会弹出一个已更改图标的弹窗,点击完成后,图标修改完成。

5、当前图标

如果想要获取当前的图标,可以创建一个变量,通过alternateIconName返回当前图标的名称。

var AlternateIconName: String {
    UIApplication.shared.alternateIconName ?? "AppIcon"
}

在图标列表中,我给底部的矩形新增了一个边框,如果AlternateIconName与当前的AppIcon相等,那么显示这个边框,否则不显示。

Button(action: {
    setAlternateIconNameFunc(name: "AppIcon\(index)")
}, label: {
    Rectangle()
        .strokeBorder(AlternateIconName == "AppIcon\(index)" ? Color(hex:"FF4B00") : .clear, lineWidth: 5)
        .foregroundColor(.white)
        .frame(width: isPadScreen ? 180 : 100,height: isPadScreen ? 180 : 100)
        .cornerRadius(10)
        .clipped()
        .overlay {
          Image(uiImage: UIImage(named: "AppIcon\(index)") ?? UIImage())
                .resizable()
                .scaledToFill()
                .frame(width: isPadScreen ? 175 : 95,height: isPadScreen ? 175 : 95)
                .cornerRadius(10)
                .clipped()
        }
})

运行代码后,在模拟器中选择灰色的图标,可以看到灰色的图标外层带有黄色的边框,表示选中。

更多细节,可以参考Apple提供的代码示例

总结

通过以上方法就可以使用应用图标的更换,还需要注意的一点,那就是Xcode项目应用名称的名称为AppIcon,因此如果通过ForEach遍历,实际上是从0到N的遍历,没有涉及到原始图标的遍历。

因此,这里可以在ForEach前面单独加一个Button按钮,显示原始图标:

Button(action: {
    setAlternateIconNameFunc(name: "AppIcon")
}, label: {
    Rectangle()
        .strokeBorder(AlternateIconName == "AppIcon" ? Color(hex:"FF4B00") : .clear, lineWidth: 5)
        .foregroundColor(.white)
        .frame(width: isPadScreen ? 180 : 100,height: isPadScreen ? 180 : 100)
        .cornerRadius(10)
        .clipped()
        .overlay {
          Image(uiImage: UIImage(named: "AppIcon") ?? UIImage())
                .resizable()
                .scaledToFill()
                .frame(width: isPadScreen ? 175 : 95,height: isPadScreen ? 175 : 95)
                .cornerRadius(10)
                .clipped()
        }
})

参考文章

1、Configuring Your App to Use Alternate App Icons:https://developer.apple.com/documentation/xcode/configuring_your_app_to_use_alternate_app_icons

2、setAlternateIconName(_:completionHandler:):https://developer.apple.com/documentation/uikit/uiapplication/setalternateiconname(_:completionhandler:)

3、Alternate App Icon Configuration in Xcode:https://www.avanderlee.com/swift/alternate-app-icon-configuration-in-xcode/

4、新版iOS应用更换图标开发教程,用户自定义图标开发:

https://blog.zhheo.com/p/9b28e469.html

完整代码

import SwiftUI

struct AppIconView: View {
    @AppStorage("20240523") var isInAppPurchase = false // 内购完成后,设置为true
    @Environment(\.layoutDirection) var layoutDirection // 获取当前语言的文字方向
    @Environment(\.dismiss) var dismiss
    @Environment(\.colorScheme) var colorScheme
    // 鸣谢页面
    
    
    let columns = [
        GridItem(.adaptive(minimum: 80, maximum: 160)), // 自动根据屏幕宽度生成尽可能多的单元格,宽度最小为 80 点
        GridItem(.adaptive(minimum: 80, maximum: 160)),
        GridItem(.adaptive(minimum: 80, maximum: 160))
    ]
    
    let columnsIpad = [
        GridItem(.adaptive(minimum: 130, maximum: 200)), // 自动根据屏幕宽度生成尽可能多的单元格,宽度最小为 80 点
        GridItem(.adaptive(minimum: 130, maximum: 200)),
        GridItem(.adaptive(minimum: 130, maximum: 200))
    ]
    
    var appIcon: [Int] {
//        return Array(isInAppPurchase ? 0..<5 : 0..<2)
        Array(0..<5)
    }
    
    var AlternateIconName: String {
        UIApplication.shared.alternateIconName ?? "AppIcon"
    }
    // 更换图标方法
    func setAlternateIconNameFunc(name: String) {
            UIApplication.shared.setAlternateIconName(name == "AppIcon" ? nil : name)
    }
    
    var body: some View {
        NavigationStack {
            GeometryReader { geometry in
                // 通过 `geometry` 获取布局信息
                let width = geometry.size.width * 0.85
                
                ZStack {
                    // 背景
                    Color(hex: colorScheme == .light ?  "f0f0f0" : "0E0E0E")
                        .ignoresSafeArea()
                    ScrollView(showsIndicators: false ) {
                        
                        LazyVGrid(columns: isPadScreen ? columnsIpad : columns,spacing: 20) {
                            Button(action: {
                                setAlternateIconNameFunc(name: "AppIcon")
                            }, label: {
                                Rectangle()
                                    .strokeBorder(AlternateIconName == "AppIcon" ? Color(hex:"FF4B00") : .clear, lineWidth: 5)
                                    .foregroundColor(.white)
                                    .frame(width: isPadScreen ? 180 : 100,height: isPadScreen ? 180 : 100)
                                    .cornerRadius(10)
                                    .clipped()
                                    .overlay {
                                      Image(uiImage: UIImage(named: "AppIcon") ?? UIImage())
                                            .resizable()
                                            .scaledToFill()
                                            .frame(width: isPadScreen ? 175 : 95,height: isPadScreen ? 175 : 95)
                                            .cornerRadius(10)
                                            .clipped()
                                    }
                                
                            })
                            ForEach(appIcon, id: \.self) { index in
                                Button(action: {
                                    setAlternateIconNameFunc(name: "AppIcon\(index)")
                                }, label: {
                                    Rectangle()
                                        .strokeBorder(AlternateIconName == "AppIcon\(index)" ? Color(hex:"FF4B00") : .clear, lineWidth: 5)
                                        .foregroundColor(.white)
                                        .frame(width: isPadScreen ? 180 : 100,height: isPadScreen ? 180 : 100)
                                        .cornerRadius(10)
                                        .clipped()
                                        .overlay {
                                          Image(uiImage: UIImage(named: "AppIcon\(index)") ?? UIImage())
                                                .resizable()
                                                .scaledToFill()
                                                .frame(width: isPadScreen ? 175 : 95,height: isPadScreen ? 175 : 95)
                                                .cornerRadius(10)
                                                .clipped()
                                        }
                                })
                            }
                        }
                        .frame(width: width)
                        .frame(maxWidth: .infinity,maxHeight: .infinity)
                        .navigationTitle("App Icon")
                        .navigationBarTitleDisplayMode(.inline)
                        .padding(.vertical,20)
                    }
                }
            }
        }
    }
}

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注