iOS 13.0 - 支持深色模式并支持 iOS 11 和 12 的最佳方法

标签 ios swift uistoryboard uicolor ios13

所以我在 Apple Developer Forums 上发过帖子,但还没有得到回复。

背景:

iOS 13 引入了深色模式和许多具有预定义的浅色和深色变体的系统颜色:( https://developer.apple.com/videos/play/wwdc2019/214/ )

这些颜色可以在 Storyboard 中直接用作命名颜色。它们也作为静态颜色添加到 UIColor 类中:( https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors )

但是,添加到 UIColor 的静态颜色在 iOS 11 和 12 的代码中不可用。这使得使用它们变得很棘手,因为对新系统颜色的所有引用都必须包含在可用性检查中:
Availability checks for UIColors

它还提出了一个问题:在 iOS 11 和 12 上,当直接在 Storyboard 中使用时,系统颜色会解析成什么?在我们的测试中,它们似乎可以解析为 Light 变体,尽管我们还没有测试所有这些变体。

当前方法:

这是我们倾向于的方法。我们将所有颜色添加到我们的 Colors.xcassets 文件以支持旧 iOS 版本,并通过我们的 CustomColors Enum 执行单个版本检查和映射,以便根据 iOS 版本返回正确的 UIColor 系统颜色。一旦我们放弃对 iOS 11 和 12 的支持,我们将从 Colors.xcassets 中删除相应的颜色,因为我们将只使用系统颜色。
我们还将重构我们所有的 Storyboard以使用新的系统颜色。

Custom color enum

这种方法的缺点是:

  • 如果在我们放弃对 iOS 11 和 12(UIColor.label、UIColor.systemBackground 等)的支持后,我们想在我们的代码中直接使用系统颜色,那么摆脱所有枚举引用
  • 可能是一个相当大的重构。
  • 因为我们将在 Storyboard 中使用系统颜色,所以我们必须确保我们的 Colors.xcassets 等效项使用相同的颜色代码
  • 此错误:(UIColor(named:) always returns nil on iOS 11.0-11.2)-如果未修复,则此方法不可用( 编辑:此错误已在 XCode 11 GM 种子 2 11A420a 中修复)
  • 与所有 Assets 目录一样,使用魔术字符串访问目录中的项目会使开发人员很容易犯错并得到 nil 而不是 Assets (本例中的颜色)。如果我们不测试每个屏幕,这可能会导致难以识别的错误,从而迫使我们编写 crashIfAllColorsNotDefined() 方法。使用枚举确实可以减轻这种风险,因为魔术字符串只在一个地方存储/使用。

  • 其他方法:(How do I easily support light and dark mode with a custom color used in my app?)

    问题:

    有哪些其他方法可以通过使用新的系统颜色来支持 iOS 13 的暗模式,同时仍支持 iOS 11 和 12?
    在旧版 iOS 上使用 Storyboard 中的新系统颜色是否安全?

    最佳答案

    Enum 和 UIColor Extension 的组合是最终的选择。
    自定义颜色有两个“部分”——应用的特殊颜色和重复的苹果颜色。

    Apple 发布的一些新颜色仅在 iOS13 或更高版本中可用(systemBackground、opaqueSeparator、secondaryLabel 等)。如果您想立即使用这些,则必须将它们创建为自定义颜色。这是一个令人担忧的问题,因为它会增加 future 的技术债务,因为一旦 iOS13 成为您支持的最低版本,这些颜色就必须重构。这在 Storyboard 中尤其难以重构。

    此解决方案的设置方式,可以轻松修改 UIColors 扩展以在稍后阶段返回官方苹果颜色。您应该只以编程方式设置重复的苹果颜色 - 不要直接在 Storyboard 中使用它们。

    在代码中:

    self.backgroundColor = .red1
    self.layer.borderColor = UIColor.successGreen1.cgColor
    

    颜色枚举:
    // Enum for all custom colors
    private enum CustomColors : String, CaseIterable {
        case red1 = "red1"
        case red2 = "red2"
        case blue1 = "blue1"
        case blue2 = "blue2"
        case successGreen1 = "successGreen1"
        case warningOrange1 = "warningOrange1"
    
        //----------------------------------------------------------------------
        // MARK: - Apple colors
        //----------------------------------------------------------------------
    
        // Duplicates for new apple colors only available in iOS 13
        case opaqueSeparator = "customOpaqueSeparator"
        case systemBackground = "customSystemBackground"
        case systemGroupedBackground = "customSystemGroupedBackground"
        case secondarySystemGroupedBackground = "customSecondarySystemGroupedBackground"
        case secondaryLabel = "customSecondaryLabel"
        case systemGray2 = "customSystemGray2"
    }
    

    UIColor 扩展:
    // Extension on UIColor for all custom (and unsupported) colors available
    extension UIColor {
    
        //----------------------------------------------------------------------
        // MARK: - Apple colors with #available(iOS 13.0, *) check
        //----------------------------------------------------------------------
    
        // These can all be removed when iOS13 becomes your minimum supported platform.
        // Or just return the correct apple-defined color instead.
    
        /// Opaque Seperator color
        static var customOpaqueSeparator: UIColor {
            if #available(iOS 13.0, *) {
                return UIColor.opaqueSeparator
            } else {
                return UIColor(named: CustomColors.opaqueSeparator.rawValue)!
            }
        }
    
        /// System Background color
        static var customSystemBackground: UIColor {
            if #available(iOS 13.0, *) {
                return UIColor.systemBackground
            } else {
                return UIColor(named: CustomColors.systemBackground.rawValue)!
            }
        }
    
        /// System Grouped Background color
        static var customSystemGroupedBackground: UIColor {
            if #available(iOS 13.0, *) {
                return UIColor.systemGroupedBackground
            } else {
                return UIColor(named: CustomColors.systemGroupedBackground.rawValue)!
            }
        }
    
        // more
    
        //----------------------------------------------------------------------
        // MARK: - My App Custom Colors
        //----------------------------------------------------------------------
    
        /// Red 1 color
        static var red1: UIColor {
            return UIColor(named: CustomColors.red1.rawValue)!
        }
    
        /// Red 2 color
        static var red2: UIColor {
            return UIColor(named: CustomColors.red2.rawValue)!
        }
    
        /// Success Green 1 color
        static var successGreen1: UIColor {
            return UIColor(named: CustomColors.successGreen1.rawValue)!
        }
    
        // more
    
        //----------------------------------------------------------------------
        // MARK: - Crash If Not Defined check
        //----------------------------------------------------------------------
    
        // Call UIColor.crashIfCustomColorsNotDefined() in AppDelegate.didFinishLaunchingWithOptions. If your application 
        // has unit tests, perhaps ensure that all colors exist via unit tests instead.
    
        /// Iterates through CustomColors enum and check that each color exists as a named color.
        /// Crashes if any don't exist.
        /// This is done because UIColor(named:) returns an optionl. This is bad - 
        /// it means that our code could crash on a particular screen, but only at runtime. If we don't coincidently test that screen
        /// during testing phase, then customers could suffer unexpected behavior.
        static func crashIfCustomColorsNotDefined() {
            CustomColors.allCases.forEach {
               guard UIColor(named: $0.rawValue) != nil else {
                Logger.log("Custom Colors - Color not defined: " + $0.rawValue)
                fatalError()
               }
           }
        }
    }
    

    在 Storyboard中:

    直接选择自定义颜色,除了重复的苹果颜色。

    颜色.xcassets:
    Colors.xcassets

    关于iOS 13.0 - 支持深色模式并支持 iOS 11 和 12 的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57719197/

    相关文章:

    swift - 硬件音量按钮和 Swift

    ios - 在两个 UITabBarController 之间切换?

    ios - 如何在 iPhone 上以低采样率(约 1000 Hz)录制

    ios - UITabBarController viewControllers 奇怪的行为

    ios - 从数组中动态添加 UIImageView

    ios - 延迟覆盖 IBOutlet 对象的属性

    ios - subview 变为 nil - UIStoryboard

    ios - 具有预定义常量值的 Interface Builder

    ios - 链接 Storyboard与 Non-Storyboard 项目

    ios - 如何在 iOS 中为一组图像制作动画