// // EPEditSettingViewController.swift // YuMi // // Created by AI on 2025-01-27. // import UIKit import Photos import SnapKit import WebKit /// 设置编辑页面 /// 支持头像更新、昵称修改和退出登录功能 class EPEditSettingViewController: BaseViewController { // MARK: - UI Components private lazy var profileImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.layer.cornerRadius = 60 // 120/2 = 60 imageView.layer.masksToBounds = true imageView.backgroundColor = .systemGray5 imageView.isUserInteractionEnabled = true return imageView }() private lazy var cameraIconView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageView.image = UIImage(named: "icon_setting_camear") imageView.backgroundColor = UIColor(hex: "#0C0527") imageView.layer.cornerRadius = 15 // 30/2 = 15 imageView.layer.masksToBounds = true return imageView }() private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = UIColor(hex: "#0C0527") tableView.separatorStyle = .none tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SettingCell") tableView.isScrollEnabled = true // 启用内部滚动 return tableView }() private lazy var logoutButton: UIButton = { let button = UIButton(type: .system) button.setTitle(YMLocalizedString("EPEditSetting.Logout"), for: .normal) button.setTitleColor(.white, for: .normal) button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) button.layer.cornerRadius = 25 button.addTarget(self, action: #selector(logoutButtonTapped), for: .touchUpInside) return button }() // MARK: - Data private var settingItems: [SettingItem] = [] private var userInfo: UserInfoModel? private var apiHelper: EPMineAPIHelper = EPMineAPIHelper() private var hasAddedGradient = false // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() setupUI() setupData() loadUserInfo() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 恢复父页面的导航栏配置(透明) restoreParentNavigationBarStyle() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 添加渐变背景到 Logout 按钮(只添加一次) if !hasAddedGradient && logoutButton.bounds.width > 0 { logoutButton.addGradientBackground( with: [ UIColor(red: 0xF8/255.0, green: 0x54/255.0, blue: 0xFC/255.0, alpha: 1.0), // #F854FC UIColor(red: 0x50/255.0, green: 0x0F/255.0, blue: 0xFF/255.0, alpha: 1.0) // #500FFF ], start: CGPoint(x: 0, y: 0.5), end: CGPoint(x: 1, y: 0.5), cornerRadius: 25 ) hasAddedGradient = true } } // MARK: - Setup private func setupNavigationBar() { title = YMLocalizedString("EPEditSetting.Title") // 配置导航栏外观(iOS 13+) let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() appearance.backgroundColor = UIColor(hex: "#0C0527") appearance.titleTextAttributes = [ .foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 18, weight: .medium) ] appearance.shadowColor = .clear // 移除底部分割线 navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance navigationController?.navigationBar.compactAppearance = appearance navigationController?.navigationBar.tintColor = .white // 返回按钮颜色 // 隐藏返回按钮文字,只保留箭头 navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) // 如果是从上一页 push 进来的,也要修改上一页的 backButtonTitle navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem( title: "", style: .plain, target: nil, action: nil ) } private func restoreParentNavigationBarStyle() { // 恢复透明导航栏(EPMineViewController 使用的是透明导航栏) let transparentAppearance = UINavigationBarAppearance() transparentAppearance.configureWithTransparentBackground() transparentAppearance.backgroundColor = .clear transparentAppearance.shadowColor = .clear navigationController?.navigationBar.standardAppearance = transparentAppearance navigationController?.navigationBar.scrollEdgeAppearance = transparentAppearance navigationController?.navigationBar.compactAppearance = transparentAppearance } private func setupUI() { view.backgroundColor = UIColor(hex: "#0C0527") // 设置头像布局 view.addSubview(profileImageView) profileImageView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(40) make.centerX.equalTo(view) make.size.equalTo(120) } // 设置相机图标布局 view.addSubview(cameraIconView) cameraIconView.snp.makeConstraints { make in make.bottom.equalTo(profileImageView.snp.bottom) make.trailing.equalTo(profileImageView.snp.trailing) make.size.equalTo(30) } // 设置 Logout 按钮布局 view.addSubview(logoutButton) logoutButton.snp.makeConstraints { make in make.leading.trailing.equalTo(view).inset(20) make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-40) make.height.equalTo(50) } // 设置 TableView 布局 view.addSubview(tableView) tableView.snp.makeConstraints { make in make.top.equalTo(profileImageView.snp.bottom).offset(40) make.leading.trailing.equalTo(view) make.bottom.equalTo(logoutButton.snp.top).offset(-20) } // 添加头像点击手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileImageTapped)) profileImageView.addGestureRecognizer(tapGesture) // 添加相机图标点击手势 let cameraTapGesture = UITapGestureRecognizer(target: self, action: #selector(profileImageTapped)) cameraIconView.addGestureRecognizer(cameraTapGesture) } private func setupData() { settingItems = [ SettingItem( title: YMLocalizedString("EPEditSetting.PersonalInfo"), action: { [weak self] in self?.handleReservedAction("PersonalInfo") } ), SettingItem( title: YMLocalizedString("EPEditSetting.Help"), action: { [weak self] in self?.handleReservedAction("Help") } ), SettingItem( title: YMLocalizedString("EPEditSetting.ClearCache"), action: { [weak self] in self?.handleReservedAction("ClearCache") } ), SettingItem( title: YMLocalizedString("EPEditSetting.AboutUs"), action: { [weak self] in self?.handleReservedAction("AboutUs") } ) ] NSLog("[EPEditSetting] setupData 完成,设置项数量: \(settingItems.count)") } private func loadUserInfo() { // 如果已经有用户信息(从 EPMineViewController 传递),则不需要重新加载 if userInfo != nil { updateProfileImage() tableView.reloadData() return } // 获取当前用户信息 guard let uid = AccountInfoStorage.instance().getUid(), !uid.isEmpty else { print("[EPEditSetting] 未登录,无法获取用户信息") return } // TODO: 调用API获取用户详细信息 // 这里暂时创建默认的UserInfoModel用于显示 let tempUserInfo = UserInfoModel() tempUserInfo.nick = "User" tempUserInfo.avatar = "" userInfo = tempUserInfo updateProfileImage() tableView.reloadData() } private func updateProfileImage() { guard let avatarUrl = userInfo?.avatar, !avatarUrl.isEmpty else { profileImageView.image = UIImage(systemName: "person.circle.fill") return } // 使用SDWebImage加载头像 if let url = URL(string: avatarUrl) { profileImageView.sd_setImage(with: url, placeholderImage: UIImage(systemName: "person.circle.fill")) } } // MARK: - Actions @objc private func profileImageTapped() { showAvatarSelectionSheet() } @objc private func openSettings() { // 预留设置按钮功能 handleReservedAction("Settings") } @objc private func logoutButtonTapped() { showLogoutConfirm() } private func showAvatarSelectionSheet() { let alert = UIAlertController(title: YMLocalizedString("EPEditSetting.EditNickname"), message: nil, preferredStyle: .actionSheet) // 拍照选项 alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Camera"), style: .default) { [weak self] _ in self?.checkCameraPermissionAndPresent() }) // 相册选项 alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.PhotoLibrary"), style: .default) { [weak self] _ in self?.checkPhotoLibraryPermissionAndPresent() }) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel)) // iPad支持 if let popover = alert.popoverPresentationController { popover.sourceView = profileImageView popover.sourceRect = profileImageView.bounds } present(alert, animated: true) } private func checkCameraPermissionAndPresent() { YYUtility.checkCameraAvailable { [weak self] in self?.presentImagePicker(sourceType: .camera) } denied: { [weak self] in self?.showPermissionAlert(title: "Camera Access", message: "Please allow camera access in Settings") } restriction: { [weak self] in self?.showPermissionAlert(title: "Camera Restricted", message: "Camera access is restricted on this device") } } private func checkPhotoLibraryPermissionAndPresent() { YYUtility.checkAssetsLibrayAvailable { [weak self] in self?.presentImagePicker(sourceType: .photoLibrary) } denied: { [weak self] in self?.showPermissionAlert(title: "Photo Library Access", message: "Please allow photo library access in Settings") } restriction: { [weak self] in self?.showPermissionAlert(title: "Photo Library Restricted", message: "Photo library access is restricted on this device") } } private func presentImagePicker(sourceType: UIImagePickerController.SourceType) { let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = sourceType imagePicker.allowsEditing = true present(imagePicker, animated: true) } private func showPermissionAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in if let settingsURL = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(settingsURL) } }) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel)) present(alert, animated: true) } private func showNicknameEditAlert() { let alert = UIAlertController( title: YMLocalizedString("EPEditSetting.EditNickname"), message: nil, preferredStyle: .alert ) alert.addTextField { [weak self] textField in textField.text = self?.userInfo?.nick ?? "" textField.placeholder = YMLocalizedString("EPEditSetting.EnterNickname") } alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel)) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Confirm"), style: .default) { [weak self] _ in guard let newNickname = alert.textFields?.first?.text, !newNickname.isEmpty else { return } self?.updateNickname(newNickname) }) present(alert, animated: true) } private func updateNickname(_ newNickname: String) { // 显示加载状态 showLoading() // 调用 API 更新昵称 apiHelper.updateNickname(withNick: newNickname, completion: { [weak self] in self?.hideHUD() // 更新成功后才更新本地显示 self?.userInfo?.nick = newNickname self?.tableView.reloadData() // 显示成功提示 self?.showSuccessToast(YMLocalizedString("XPMineUserInfoEditViewController13")) print("[EPEditSetting] 昵称更新成功: \(newNickname)") }, failure: { [weak self] (code: Int, msg: String?) in self?.hideHUD() // 显示错误提示 let errorMsg = msg ?? YMLocalizedString("setting.nickname_update_failed") self?.showErrorToast(errorMsg) print("[EPEditSetting] 昵称更新失败: \(code) - \(errorMsg)") } ) } private func showLogoutConfirm() { let alert = UIAlertController( title: YMLocalizedString("EPEditSetting.LogoutConfirm"), message: nil, preferredStyle: .alert ) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel)) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Logout"), style: .destructive) { [weak self] _ in self?.performLogout() }) present(alert, animated: true) } private func performLogout() { guard let account = AccountInfoStorage.instance().accountModel else { print("[EPEditSetting] 账号信息不存在") return } // 调用登出API Api.logoutCurrentAccount({ [weak self] (data, code, msg) in DispatchQueue.main.async { // 清除本地数据 AccountInfoStorage.instance().saveAccountInfo(nil) AccountInfoStorage.instance().saveTicket(nil) // 跳转登录页 self?.navigateToLogin() } }, access_token: account.access_token) } private func navigateToLogin() { let loginVC = EPLoginViewController() let nav = UINavigationController(rootViewController: loginVC) if let window = UIApplication.shared.windows.first { window.rootViewController = nav window.makeKeyAndVisible() } print("[EPEditSetting] 已跳转到登录页面") } private func handleReservedAction(_ title: String) { print("[\(title)] - 功能触发") // About Us 已实现 if title == "AboutUs" { let aboutVC = EPAboutUsViewController() navigationController?.pushViewController(aboutVC, animated: true) return } // Personal Info - 显示协议和隐私政策选项 if title == "PersonalInfo" { showPolicyOptionsSheet() return } // Help - 跳转到 FAQ 页面 if title == "Help" { let faqUrl = getFAQURL() openPolicyInExternalBrowser(faqUrl) return } // Clear Cache - 清理图片和网页缓存 if title == "ClearCache" { showClearCacheConfirmation() return } // 其他功能预留 // TODO: Phase 2 implementation let alert = UIAlertController(title: "Coming Soon", message: "This feature will be available in the next update.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } private func showClearCacheConfirmation() { let alert = UIAlertController( title: YMLocalizedString("EPEditSetting.ClearCacheTitle"), message: YMLocalizedString("EPEditSetting.ClearCacheMessage"), preferredStyle: .alert ) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel)) alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Confirm"), style: .destructive) { [weak self] _ in self?.performClearCache() }) present(alert, animated: true) } private func performClearCache() { print("[EPEditSetting] 开始清理缓存") // 显示加载状态 showLoading() // 1. 清理 SDWebImage 图片缓存 SDWebImageManager.shared.imageCache.clear?(with: .all) { print("[EPEditSetting] SDWebImage 缓存已清理") // 2. 清理 WKWebsiteDataStore 网页缓存 let websiteDataTypes = WKWebsiteDataStore.allWebsiteDataTypes() let dateFrom = Date(timeIntervalSince1970: 0) WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom) { [weak self] in print("[EPEditSetting] WKWebsiteDataStore 缓存已清理") DispatchQueue.main.async { self?.hideHUD() self?.showSuccessToast(YMLocalizedString("EPEditSetting.ClearCacheSuccess")) print("[EPEditSetting] 缓存清理完成") } } } } private func showPolicyOptionsSheet() { let alert = UIAlertController( title: nil, message: nil, preferredStyle: .actionSheet ) // 用户服务协议 alert.addAction(UIAlertAction( title: YMLocalizedString("EPEditSetting.UserAgreement"), style: .default ) { [weak self] _ in let url = self?.getUserAgreementURL() ?? "" self?.openPolicyInExternalBrowser(url) }) // 隐私政策 alert.addAction(UIAlertAction( title: YMLocalizedString("EPEditSetting.PrivacyPolicy"), style: .default ) { [weak self] _ in let url = self?.getPrivacyPolicyURL() ?? "" self?.openPolicyInExternalBrowser(url) }) // 取消 alert.addAction(UIAlertAction( title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel )) // iPad 支持 if let popover = alert.popoverPresentationController { popover.sourceView = view popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0) popover.permittedArrowDirections = [] } present(alert, animated: true) } /// 获取用户协议 URL private func getUserAgreementURL() -> String { // kUserProtocalURL 对应枚举值 4 let url = URLWithType(URLType(rawValue: 4)!) as String print("[EPEditSetting] User agreement URL from URLWithType: \(url)") return url } /// 获取隐私政策 URL private func getPrivacyPolicyURL() -> String { // kPrivacyURL 对应枚举值 0 let url = URLWithType(URLType(rawValue: 0)!) as String print("[EPEditSetting] Privacy policy URL from URLWithType: \(url)") return url } /// 获取 FAQ 帮助页面 URL private func getFAQURL() -> String { // kFAQURL 对应枚举值 6 let url = URLWithType(URLType(rawValue: 6)!) as String print("[EPEditSetting] FAQ URL from URLWithType: \(url)") return url } private func openPolicyInExternalBrowser(_ urlString: String) { print("[EPEditSetting] Original URL: \(urlString)") // 如果不是完整 URL,拼接域名 var fullUrl = urlString if !urlString.hasPrefix("http") && !urlString.hasPrefix("https") { let hostUrl = HttpRequestHelper.getHostUrl() fullUrl = "\(hostUrl)/\(urlString)" print("[EPEditSetting] Added host URL, full URL: \(fullUrl)") } print("[EPEditSetting] Opening URL in external browser: \(fullUrl)") guard let url = URL(string: fullUrl) else { print("[EPEditSetting] ❌ Invalid URL: \(fullUrl)") return } print("[EPEditSetting] URL object created: \(url)") // 在外部浏览器中打开 if UIApplication.shared.canOpenURL(url) { print("[EPEditSetting] ✅ Can open URL, attempting to open...") UIApplication.shared.open(url, options: [:]) { success in print("[EPEditSetting] Open external browser: \(success ? "✅ Success" : "❌ Failed")") } } else { print("[EPEditSetting] ❌ Cannot open URL: \(fullUrl)") } } // MARK: - Public Methods /// 更新用户信息(从 EPMineViewController 传递) @objc func updateWithUserInfo(_ userInfo: UserInfoModel) { self.userInfo = userInfo updateProfileImage() tableView.reloadData() NSLog("[EPEditSetting] 已更新用户信息: \(userInfo.nick)") } } // MARK: - UITableViewDataSource & UITableViewDelegate extension EPEditSettingViewController: UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 // 只有一个 section } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let count = settingItems.count + 1 // +1 for nickname row NSLog("[EPEditSetting] TableView rows count: \(count), settingItems: \(settingItems.count)") return count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "SettingCell", for: indexPath) cell.backgroundColor = UIColor(hex: "#0C0527") cell.textLabel?.textColor = .white cell.selectionStyle = .none // 清除之前的自定义视图 cell.contentView.subviews.forEach { $0.removeFromSuperview() } if indexPath.row == 0 { // 昵称行 cell.textLabel?.text = YMLocalizedString("EPEditSetting.Nickname") // 添加右箭头图标 let arrowImageView = UIImageView() arrowImageView.image = UIImage(named: "icon_setting_right_arrow") arrowImageView.contentMode = .scaleAspectFit cell.contentView.addSubview(arrowImageView) arrowImageView.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-20) make.centerY.equalToSuperview() make.size.equalTo(22) } // 添加用户昵称标签 let nicknameLabel = UILabel() nicknameLabel.text = userInfo?.nick ?? YMLocalizedString("user.not_set") nicknameLabel.textColor = .lightGray nicknameLabel.font = UIFont.systemFont(ofSize: 16) cell.contentView.addSubview(nicknameLabel) nicknameLabel.snp.makeConstraints { make in make.trailing.equalTo(arrowImageView.snp.leading).offset(-12) make.centerY.equalToSuperview() } } else { // 其他设置项 let item = settingItems[indexPath.row - 1] cell.textLabel?.text = item.title cell.textLabel?.textColor = .white // 添加右箭头图标 let arrowImageView = UIImageView() arrowImageView.image = UIImage(named: "icon_setting_right_arrow") arrowImageView.contentMode = .scaleAspectFit cell.contentView.addSubview(arrowImageView) arrowImageView.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-20) make.centerY.equalToSuperview() make.size.equalTo(22) } } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60 // 所有行都是 60pt 高度 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.row == 0 { // 昵称点击 showNicknameEditAlert() } else { // 设置项点击 let item = settingItems[indexPath.row - 1] item.action() } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return nil } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0 } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return nil } } // MARK: - UIImagePickerControllerDelegate & UINavigationControllerDelegate extension EPEditSettingViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) guard let image = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage else { print("[EPEditSetting] 未能获取选择的图片") return } // 更新头像显示 profileImageView.image = image // 上传头像到腾讯云 uploadAvatar(image) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) } private func uploadAvatar(_ image: UIImage) { // 显示上传进度 EPProgressHUD.showProgress(0, total: 1) // 使用 EPSDKManager 统一上传接口(避免腾讯云 OCR 配置问题) EPSDKManager.shared.uploadImages([image], progress: { uploaded, total in EPProgressHUD.showProgress(uploaded, total: total) }, success: { [weak self] resList in EPProgressHUD.dismiss() guard !resList.isEmpty, let firstRes = resList.first, let avatarUrl = firstRes["resUrl"] as? String else { print("[EPEditSetting] 头像上传成功但无法获取URL") return } print("[EPEditSetting] 头像上传成功: \(avatarUrl)") // 调用API更新头像 self?.updateAvatarAPI(avatarUrl: avatarUrl) }, failure: { [weak self] errorMsg in EPProgressHUD.dismiss() print("[EPEditSetting] 头像上传失败: \(errorMsg)") // 显示错误提示 DispatchQueue.main.async { let alert = UIAlertController(title: YMLocalizedString("common.upload_failed"), message: errorMsg, preferredStyle: .alert) alert.addAction(UIAlertAction(title: YMLocalizedString("common.confirm"), style: .default)) self?.present(alert, animated: true) } } ) } private func updateAvatarAPI(avatarUrl: String) { // 使用 API Helper 更新头像 apiHelper.updateAvatar(withUrl: avatarUrl, completion: { [weak self] in print("[EPEditSetting] 头像更新成功") // 更新本地用户信息 self?.userInfo?.avatar = avatarUrl // 通知父页面头像已更新 self?.notifyParentAvatarUpdated(avatarUrl) }, failure: { [weak self] (code: Int, msg: String?) in print("[EPEditSetting] 头像更新失败: \(code) - \(msg ?? "未知错误")") // 显示错误提示 DispatchQueue.main.async { let alert = UIAlertController( title: YMLocalizedString("common.update_failed"), message: msg ?? YMLocalizedString("setting.avatar_update_failed"), preferredStyle: .alert ) alert.addAction(UIAlertAction(title: YMLocalizedString("common.confirm"), style: .default)) self?.present(alert, animated: true) } }) } private func notifyParentAvatarUpdated(_ avatarUrl: String) { // 发送通知给 EPMineViewController 更新头像 let userInfo = ["avatarUrl": avatarUrl] NotificationCenter.default.post(name: NSNotification.Name("EPEditSettingAvatarUpdated"), object: nil, userInfo: userInfo) } } // MARK: - Helper Models private struct SettingItem { let title: String let action: () -> Void init(title: String, action: @escaping () -> Void) { self.title = title self.action = action } } // MARK: - UIColor Extension private extension UIColor { convenience init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (1, 1, 1, 0) } self.init( red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255 ) } }