Icon of Package

BezelKit

完美边角,每次一个弧度

Swift Versions

Platforms

Licence

概述

BezelKit 是一个 Swift 包,旨在简化在应用中访问设备特定边框尺寸的过程。

了解确切的边框尺寸对于对齐 UI 元素、创建沉浸式体验或在您需要像素级完美的设计布局时至关重要。

通过提供易于使用的 API,BezelKit 使开发者能够更专注于应用的功能,而不是与设备指标作斗争。

原理

快速摘要

更长的解释

Apple 目前没有提供公开 API 来获取其设备的边框半径。

尽管存在内部 API,但使用它会危及应用在 App Store 中的资格——对于仅仅是一个 UI 元素而言,这种风险是不值得的。

另一个考虑因素源于不同 Apple 设备之间屏幕边框尺寸的可变性。为静态边框值设置是成问题的,原因如下:

  1. 如果实际边框半径小于或大于静态值,则 UI 角将显得不成比例地粗或细。

    Zoomed - Static Value

  2. 在较旧的设备或具有方形屏幕的设备(例如 SE 型号)上,显示屏将不准确地呈现曲角,而它本不应该有。

虽然 Apple 提供了 ContainerRelativeShape 内边距,但其功能目前仅限于 Widgets。对于所有其他应用程序,此 API 报告一个正方形矩形,使其不适合我们的需求。

一个美观的解决方案看起来像这样

Zoomed - BezelKit

兼容性

设备

就支持的设备而言,它涵盖了所有设备的初始版本。请参阅受支持的设备列表

安装

Swift Package Manager

BezelKit 包使用 Swift Package Manager (SPM) 以实现简单便捷的分发。请按照以下步骤将其添加到您的项目中

  1. 在 Xcode 中,点击 File -> Swift Packages -> Add Package Dependency

  2. 在搜索栏中,输入 https://github.com/markbattistella/BezelKit 并点击 Next

  3. 指定您要使用的版本。您可以选择确切的版本、使用最新版本或设置版本范围,然后点击 Next

    [!Tip] 最好查看更改日志以了解不同版本之间的差异

  4. 最后,选择您想要使用 BezelKit 的目标,然后点击 Finish

用法

使用 BezelKit 非常简单,可以帮助您避免与设备指标相关的复杂性。

快速开始

  1. 导入 BezelKit 模块

    import BezelKit
  2. 访问设备边框尺寸

    let currentBezel = CGFloat.deviceBezel

有关高级用法,包括完美缩放 UI 元素和设置回退尺寸,请阅读以下章节。

完美缩放

BezelKit 包不仅提供了一种访问设备特定边框尺寸的简便方法,还可以实现 UI 中圆角的完美缩放。

当您在外层有一个圆角,并且内部 UI 元素也需要一个圆角时,保持完美的纵横比对于和谐的设计至关重要。这确保您的 UI 可以在不同的设备上完美缩放。

以下是如何实现它

let outerBezel = CGFloat.BezelKit
let innerBezel = outerBezel - distance  // Perfect ratio

通过遵循这种方法,您可以确保您的 UI 元素相对于设备的边框尺寸完美缩放。

Perfect scaling

您可以使用 deviceBezel(with:) 函数传入边距大小,它将返回设备边框,但会根据内部比率完美缩放。

设置回退边框尺寸

该软件包提供了一种指定回退边框尺寸的简便方法。默认情况下,如果 CGFloat.deviceBezel 属性无法确定设备的边框尺寸,则返回 0.0

启用零值检查选项

除了指定回退值之外,您还可以选择即使在确定边框尺寸为零时也返回回退值。

UIKit:在 AppDelegate 中设置回退值

对于基于 UIKit 的应用程序,您可以在 AppDelegate 中的 application(_:didFinishLaunchingWithOptions:) 方法中设置回退值。这是您可以为您的应用程序设置回退值的最早时间点。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // Sets a fallback value of 10.0 and enables zero-check
    CGFloat.setFallbackDeviceBezel(10.0, ifZero: true)
    
    return true
  }
}

SwiftUI:在 Appear 时设置回退值

对于 SwiftUI 应用程序,您可以在主内容视图的 init() 函数中设置此值。

import SwiftUI

@main
struct YourApp: App {
  init() {
    // Sets a fallback value of 10.0 and enables zero-check
    CGFloat.setFallbackDeviceBezel(10.0, ifZero: true)
  }
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

重要提示

之前有人指出使用 .onAppear 修饰符来设置回退值。这导致了一个启动时不更新的错误

注意

您只需要调用 setFallbackDeviceBezel(_:ifZero:) 一次。尽早执行此操作可确保回退设置应用于您的整个应用程序。

设置回退值的影响

如果您已设置回退值,则当 CGFloat.deviceBezel 无法确定当前设备的边框尺寸时,或者在可选情况下,当边框尺寸为零时,它将返回此回退值。

// With fallback set to 10.0 and zero check enabled
let currentBezel = CGFloat.deviceBezel
print("Current device bezel: \(currentBezel)")

// Output will be 10.0 if the device is not in the JSON data or the bezel is zero

如果未设置回退值,则当设备特定的边框尺寸不可用时,CGFloat.BezelKit 默认为 0.0

// With no fallback set and zero check disabled
let currentBezel = CGFloat.deviceBezel
print("Current device bezel: \(currentBezel)")

// Output will be 0.0 if the device is not in the JSON data

使用 BezelKit 处理错误

BezelKit 提供可选的错误处理来管理意外问题,例如缺少设备边框数据或数据解析问题。

通过使用 BezelKit 的错误回调,开发者可以收到这些小故障的警报,从而使他们能够根据自己的意愿处理这些问题,无论是记录以进行调试还是用户通知。

这确保了更流畅和更具弹性的应用程序体验。

SwiftUI:错误处理

import SwiftUI
import BezelKit

struct ContentView: View {
  @State private var showErrorAlert: Bool = false
  @State private var errorMessage: String = ""

  var body: some View {
    RoundedRectangle(cornerRadius: .deviceBezel)
      .stroke(Color.green, lineWidth: 20)
      .ignoresSafeArea()
      .alert(isPresented: $showErrorAlert) {
        Alert(title: Text("Error"),
              message: Text(errorMessage),
              dismissButton: .default(Text("Got it!")))
      }
      .onAppear {
          DeviceBezel.errorCallback = { error in
            errorMessage = error.localizedDescription
            showErrorAlert = true
          }
      }
    }
}

UIKit:错误处理

import UIKit
import BezelKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    DeviceBezel.errorCallback = { [weak self] error in
      let alert = UIAlertController(title: "Error",
                                    message: error.localizedDescription,
                                    preferredStyle: .alert)
      alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
      self?.present(alert, animated: true, completion: nil)
    }
        
    let bezelValue = CGFloat.deviceBezel
    // Use bezelValue for your views
  }
}

对比

这是一个比较,比较了对所有设备使用静态、单一值,以及与 BezelKit 相比,渲染时的外观,BezelKit 将适应每个设备。

这是对所有设备使用静态、单一值时的代码

import SwiftUI

struct ContentView: View {
  var body: some View {
    RoundedRectangle(cornerRadius: 60)
      .stroke(.green, lineWidth: 20)
      .ignoresSafeArea()
  }
}

Comparison - Static Values

在固定值配置中,没有曲面屏幕的设备看起来很奇怪,虽然此 cornerRadius 是为 iPhone 14 Pro Max 设计的,但在 iPhone 14 上看起来很笨重,在 iPhone 14 Pro 上看起来还不错

这是使用 BezelKit 时的代码

import SwiftUI
import BezelKit

struct ContentView: View {
  var body: some View {
    RoundedRectangle(cornerRadius: .deviceBezel)
      .stroke(.green, lineWidth: 20)
      .ignoresSafeArea()
  }
}

Comparison - BezelKit

正如您所见,在没有设置 setFallbackBezelKit 的情况下,iPhone SE(第三代)的值设置为 0.0,并且没有曲线。但是,所有其他曲面设备都具有一致的外观。

注意事项

BezelKit 目前不 支持显示缩放的影响。当生成器运行时,它会在设备的“标准”缩放级别上执行所有提取。

如果在“缩放”级别上运行,则边框半径将有所不同。但是,由于物理设备无法根据缩放级别进行更改,因此使用“标准”是正确的 CGFloat 数字。

xcrun simctl 中也没有自动缩放级别的方法,因此它必须是手动包含,并且在此时(除非通过 Issues 提出),使用缩放值 _displayRoundedCorner 并没有真正的益处。

生成新的边框

有关生成新边框,请参阅 BezelKit - Generator 存储库。

运行脚本时,最好从 BezelKit 目录执行,因为其中一个脚本行是将编译后的 JSON 复制到 /Resources 目录。从生成器仓库的角度来看,这将不存在。

贡献

非常欢迎贡献。如果您发现错误或有增强功能的想法,请打开一个 issue 或提供一个 pull request。

在做出贡献时,请遵循当前代码库中存在的代码风格。

注意

任何 pull request 都需要具有以下格式的标题,否则将被拒绝。

YYYY-mm-dd - {title}
eg. 2023-08-24 - Updated README file

我喜欢跟踪从记录请求到完成的日期,以便进行排序和数据提取。它可以帮助我了解事情已经待处理了多久,并提供 OCD 结构。

许可

BezelKit 包在 MIT 许可下发布。有关更多信息,请参阅 LICENCE