在前文《SwiftUI更换应用图标》中,谈到使用UIApplication.shared.setAlternateIconName方法更换图标。
但是在更换图标的过程中,会存在一个待用户确认的提示框,这个提示框由UIApplication.shared.setAlternateIconName方法调用并弹出。
屏蔽系统弹窗代码
在Swift中无法屏蔽这一弹窗,但是可以借助UIKit代码屏蔽这一弹窗。

我们需要使用UIKit和Swift代码:
class TransparentViewController: UIViewController {
override func viewDidLoad() {
print("进入到 TransparentViewController 的 viewDidLoad方法")
super.viewDidLoad()
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.0) // 完全透明
backgroundView.frame = view.bounds
view.addSubview(backgroundView)
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if viewControllerToPresent is UIAlertController {
print("拦截了系统弹窗")
dismiss(animated: false)
completion?()
} else {
super.present(viewControllerToPresent, animated: flag,completion: completion)
}
}
}
class IconChanger {
static func changeIconSilently(to name: String?,selected: Binding<String>) {
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }),
let rootVC = windowScene.windows.first?.rootViewController else {
// 安全地拿到当前活跃窗口的根控制器
print("无法找到 rootVC,退出方法")
return
}
var topVC = rootVC
while let presented = topVC.presentedViewController {
topVC = presented
}
let transparentVC = TransparentViewController()
transparentVC.modalPresentationStyle = .overFullScreen
if !(topVC is TransparentViewController) && !(topVC is UIAlertController) {
topVC.present(transparentVC, animated: false)
if UIApplication.shared.supportsAlternateIcons {
print("支持功能图标的功能")
UIApplication.shared.setAlternateIconName(name)
DispatchQueue.main.async {
// 修改存储的图标名称
AppStorageManager.shared.appIcon = name ?? "AppIcon 2"
selected.wrappedValue = AppStorageManager.shared.appIcon
}
} else {
print("不支持更换图标功能")
}
} else {
// 已有其他控制器,无法静默处理
print("topVC 判断出错,设置 App Icon 出错")
}
}
}
在SwiftUI中使用IconChanger的changeIconSilently方法更换图标:
struct AppIconView: View {
@State private var selectedIconName: String = UIApplication.shared.alternateIconName ?? "AppIcon 2"
var body: some View {
// 图标按钮
Button(action: {
IconChanger.changeIconSilently(to: "AppIcon \(index)",selected: $selectedIconName)
}, label: {
// 图标
}
}
}
在点击图标按钮后,不再弹出系统弹窗。

解析屏蔽系统弹窗代码
可以通过修改上面的代码来实现屏蔽系统弹窗,下面是代码是如何实现屏蔽系统弹窗的。
在SwiftUI中无法实现这个功能,原因在于SwiftUI是自动管理状态和界面。而UIKit可以管理控制器堆栈,因此当涉及系统弹窗时,UIKit能够检测到系统弹窗并将其拦截,然后返回到原来的页面中。
1、SwiftUI视图代码
首先来看SwiftUI视图代码,这里的selectedIconName是获取图标的名称,通过UIApplication.shared.alternateIconName可以获取到当前应用的图标。
在SwiftUI视图中,当点击对应的图标时,会调用Button中的方法。

struct AppIconView: View {
@State private var selectedIconName: String = UIApplication.shared.alternateIconName ?? "AppIcon 2"
var body: some View {
// 图标按钮
Button(action: {
IconChanger.changeIconSilently(to: "AppIcon \(index)",selected: $selectedIconName)
}, label: {
// 图标
}
}
}
2、changeIconSilently方法
IconChanger类中有一个changeIconSilently静态方法:
class IconChanger {
static func changeIconSilently(to name: String?,selected: Binding<String>) {
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }),
let rootVC = windowScene.windows.first?.rootViewController else {
// 安全地拿到当前活跃窗口的根控制器
print("无法找到 rootVC,退出方法")
return
}
var topVC = rootVC
while let presented = topVC.presentedViewController {
topVC = presented
}
let transparentVC = TransparentViewController()
transparentVC.modalPresentationStyle = .overFullScreen
if !(topVC is TransparentViewController) && !(topVC is UIAlertController) {
topVC.present(transparentVC, animated: false)
if UIApplication.shared.supportsAlternateIcons {
print("支持功能图标的功能")
UIApplication.shared.setAlternateIconName(name)
DispatchQueue.main.async {
// 修改存储的图标名称
AppStorageManager.shared.appIcon = name ?? "AppIcon 2"
selected.wrappedValue = AppStorageManager.shared.appIcon
}
} else {
print("不支持更换图标功能")
}
} else {
// 已有其他控制器,无法静默处理
print("topVC 判断出错,设置 App Icon 出错")
}
}
}
这个静态方法接收两个参数,分别是name和selected。
static func changeIconSilently(to name: String?,selected: Binding<String>) { }
name为修改的图标名称,用于UIApplication.shared.setAlternateIconName更换图标的方法。selected是SwiftUI中当前图标的名称。
当更换图标成功后,可以将selected修改为新的图标名称,然后可以通过这一变量配置图标选中等效果,这里不过多累赘。
进入到changeIconSilently方法后,我们获取到了rootVC根控制器。
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }),
let rootVC = windowScene.windows.first?.rootViewController else {
// 安全地拿到当前活跃窗口的根控制器
print("无法找到 rootVC,退出方法")
return
}
这段代码表示从UIApplication(UIKit框架单例模式)中获取当前App正在前台活动(foregroundActive)的UI界面(UIWindowScene)的窗口数组。

通过windowScene获取第一个窗口,也就是主窗口rootVC。这部分内容可以参考《iOS窗口容器UIWindow》的“与SwiftUI的关系”部分。
为什么需要获取rootVC根控制器?获取它做什么呢?
因为rootVC(rootViewController简称)根控制器是整个界面的起点,我们通过获取rootVC根控制器操作界面。

例如,以“存钱猪猪”应用为例,这个应用窗口就是UIWindow,rootViewController根控制器就是底部的主视图。弹出的“统计”Sheet视图就是其他视图。
层级关系为rootViewController根控制器在底部,上面是Sheet视图。
当我们获取到rootViewController根控制器后,就可以控制底部的主视图,比如嵌套SwiftUI视图、弹出提示框等。也可以获取到rootViewController根控制器的上层控制器Sheet,操作Sheet视图。
下一步就是获取到最顶层的控制器,也就是视图。
var topVC = rootVC
while let presented = topVC.presentedViewController {
topVC = presented
}
原因在于,我们需要在最顶层的控制器中弹出视图,而不是在底部rootViewController根控制器或者中间的控制器中,弹出视图。
如果不在最顶层弹出视图,那么提示框可能被覆盖或者不显示,因此提示框应该在所有视图的最顶部。
然后创建一个自定义的TransparentViewController控制器,并设置为全屏显示。
let transparentVC = TransparentViewController()
transparentVC.modalPresentationStyle = .overFullScreen
按照代码的执行逻辑,进入到TransparentViewController控制器。
3、TransparentViewController控制器
TransparentViewController控制器是一个UIViewController,可以理解为视图,但是比SwiftUI更复杂,它可以加载并管理视图,控制视图的显示、消失和加载等等行为。
class TransparentViewController: UIViewController {
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if viewControllerToPresent is UIAlertController {
print("拦截了系统弹窗")
dismiss(animated: false)
completion?()
} else {
super.present(viewControllerToPresent, animated: flag,completion: completion)
}
}
}
在TransparentViewController类中,使用override重写了present(弹窗)方法。
这就表示,如果在TransparentViewController控制器中,系统想要显示弹窗,就会调用TransparentViewController控制器的present方法。该方法会判断视图控制器是否为提示框。
如果弹出的控制器类型是提示框,因为present方法被我重写了,提示框就不会继承父类的行为,这也意味着不会弹出。而是执行我重写的代码:
if viewControllerToPresent is UIAlertController {
print("拦截了系统弹窗")
dismiss(animated: false)
completion?()
}
在重写的代码中,我没有执行其他的操作,而是调用dismiss方法,这个方法会将TransparentViewController控制器关闭,返回上一个控制器。随后调用了闭包,当然这个闭包貌似没有什么用,但我为了遵循方法还是调用一下。
否则就使用super正常调用父类的行为,弹出不是提示框的窗口。
4、返回到changeIconSilently方法
下面是判断最顶层的控制器是否是自定义的TransparentViewController控制器或者是系统提示框。
if !(topVC is TransparentViewController) && !(topVC is UIAlertController) {
topVC.present(transparentVC, animated: false)
if UIApplication.shared.supportsAlternateIcons {
print("支持功能图标的功能")
UIApplication.shared.setAlternateIconName(name)
DispatchQueue.main.async {
// 修改存储的图标名称
AppStorageManager.shared.appIcon = name ?? "AppIcon 2"
selected.wrappedValue = AppStorageManager.shared.appIcon
}
} else {
print("不支持更换图标功能")
}
} else {
// 已有其他控制器,无法静默处理
print("topVC 判断出错,设置 App Icon 出错")
}
如果判断成功的话,表示现在已经有自定义控制器或提示框显示,就不再显示自定义的控制。
if !(topVC is TransparentViewController) && !(topVC is UIAlertController) { ... }
如果最顶层的控制器既不是自定义的TransparentViewController控制器,也不是系统提示框,那么就调用最顶层控制器的present方法。
topVC.present(transparentVC, animated: false)
present是UIViewController的方法,表示弹出一个新的控制器。也可以理解为SwiftUI中的ZStack新增一个最上层的视图。这样,新的控制器就会在旧的控制器之上显示。
然后判断iOS系统是否支持更换图标的功能,如果支持,则调用更换图标的方法,并将传入的name参数传递进去。
这个UIApplication.shared.setAlternateIconName是如何实现的呢?可以理解为系统会在最顶层的控制器中,调用一个系统弹窗方法,提示用户确认弹窗,确认后修改图标。
现在最顶层的控制器就是我们自定义的一个控制器TransparentViewController,我们还重写了present方法,所以系统就会调用最顶层控制器TransparentViewController的present方法来调用系统弹窗,我们重写的present方法是监测弹窗的类型,如果是提示框,就关闭TransparentViewController控制器,其他类型则正常弹出。

所以,当UIApplication.shared.setAlternateIconName实际调用系统弹窗时,被我们重写的present方法忽略掉系统弹窗,因此就会实现没有系统弹窗的功能。
这就是本文的核心内容,然后通过DispatchQueue.main.async更新主线程的图标代码,这里就是根据项目而定了。
总结
本文的核心内容是通过UIWindow的根控制器来管理其他的控制器。还涉及到UIViewController的present弹出控制器方法。
在文章中提及的“控制器”可以理解为视图或者页面,在UIKit中常用控制器来表示视图或者页面。
参考文章
1、惊人开发技巧:轻松更换 App 图标,无需系统弹窗!:https://mp.weixin.qq.com/s/-wGkKPRTz7aYvMxsN3PcFg
2、SwiftUI更换应用图标:https://fangjunyu.com/2025/01/28/swiftui%e6%9b%b4%e6%8d%a2%e5%ba%94%e7%94%a8%e5%9b%be%e6%a0%87/
3、Swift知识扩展:Static静态方法的实际运用:https://fangjunyu.com/2024/10/17/swift%e7%9f%a5%e8%af%86%e6%89%a9%e5%b1%95%ef%bc%9astatic%e9%9d%99%e6%80%81%e6%96%b9%e6%b3%95%e7%9a%84%e5%ae%9e%e9%99%85%e8%bf%90%e7%94%a8/
4、iOS窗口容器UIWindow:https://fangjunyu.com/2025/05/20/ios%e7%aa%97%e5%8f%a3%e5%ae%b9%e5%99%a8uiwindow/
5、SwiftUI和iOS核心类UIViewController:https://fangjunyu.com/2025/05/19/swiftui%e5%92%8cios%e6%a0%b8%e5%bf%83%e7%b1%bbuiviewcontroller/
6、Swift重写父类的override:https://fangjunyu.com/2025/05/18/swift%e9%87%8d%e5%86%99%e7%88%b6%e7%b1%bb%e7%9a%84override/
7、Swift引用父类的super:https://fangjunyu.com/2025/05/19/swift%e5%bc%95%e7%94%a8%e7%88%b6%e7%b1%bb%e7%9a%84super/