一些新东西

预览

用的#Preview宏, 这里主要讲预览UIKit.

// viewcontroller
class LibraryViewController: UIViewController {
    // ...
}

#Preview("Library") {
    let controller = LibraryViewController()
    controller.displayCuratedContent = true
    return controller
}

// view
class SlideshowView: UIView {
    // ...
}
#Preview("Memories") { // You can also specify name of preview here `Memories` will appear as a tab
    let view = SlideshowView()
    view.title = "Memories"
    view.subtitle = "Highlights from the past year"
    view.images = ...
    return view
}

空状态

iOS17 开始提供了一系列空状态视图,用于在数据为空时显示友好的提示信息。比如加载中(loading), 无数据(empty), 无搜索结果(search)等, 同任何其他系统组件一件, 你需要操作一堆内置属性来自定义, 如果你不是很熟它的结构, UI稿又和默认UI不一样, 花的时间可能跟自己写一个差不多, 毕竟只是一个静态视图, 但如果你只是想快速实现一个空状态, 那么它还是很有用的。

var config = UIContentUnavailableConfiguration.empty()
config.image = UIImage(systemName: "star.fill")
config.text = "No"
config.secondaryText = "Your favorite translations will appear here."
// setting contentUnavailableConfiguration
viewController.contentUnavailableConfiguration = config

通过text, image, button及一堆关联的properties来个性化.

更新状态的时机:

// Represent no search results empty state
override func updateContentUnavailableConfiguration(using state: UIContentUnavailableConfigurationState) {
    var config: UIContentUnavailableConfiguration?
    if searchResults.isEmpty {
        config = .search()
    }
    // 可见, 设为nil就能移除
    contentUnavailableConfiguration = config
}

// Update search results for query
searchResults = backingStore.results(for: query)
setNeedsUpdateContentUnavailableConfiguration()

经过我的测试, unavailable视图只会把特定的图标文字覆盖在当前视图上, 不会全屏覆盖, 所以旧视图的清理工作还是需要你自己做的.

动图

// Bounce the symbol once
imageView.addSymbolEffect(.bounce)

// 让颜色由深到浅进行动画(可以循环播放, 直到你remove)
// Add a variable color effect, which repeats
imageView.addSymbolEffect(.variableColor.iterative)

// Somtime later, remove the effect
imageView.removeSymvolEffect(ofType:.variableColor)

// Change the image, using Replace effect 
imageView.setSymbolImage (pauseImage, contentTransition: .replace.offUp)

前提, 使用SF symbols.

see

字符图片的国际化变体

仍然是sF symbols的特性, 同一个字符在不同国家有不同表示, 可以结合NSLocale使用, 这次来一版OC的:

NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"];
UIImageConfiguration *confg = [UIImageConfiguration configurationWithLocale:locale];
[UIImage systemImageNamed:@"character.textbox" withConfiguration:conf];

其中ja是语言JP是地区, 也可以用NSLocalecurrent属性.()

swift则可以使用Locale:

// Retrieve UIImage by locale
let locale = Locale (languageCode: •japanese)

imageView.image = UIImage (
systemName: "character.textbox",
withConfiguration: UIImage. SymbolConfiguration(locale: locale)
)

PageControl支持自动翻页和跟踪翻页进度

let timerProgress = UIPageControlTimerProgress(preferredDuration: 10)
pageControl progress = timerProgress
timerProgress.resumeTimer ()

标识当前页的圆点会变成一个进度条.

如果你要跟踪轮播图里的视频进度, 或外部定时器, 可以使用ULPagecontrolProgress, 把当前时长与总时长除一下得到一个进度, 传给currentProgress属性即可.:

let progress = ULPagecontrolProgress()
pageControl.progress = progress
myTimer.addPeriodicTimeObserver { timer in
    progress.currentProgress = Float(timer.seconds / timer.duration)
// ...
}

SwiftUI ScrollView

WWDC23 10159

colorStack
	.aspectRatio(16.0/9.0, contentMode: .fit)
    // 自适应内容大小(演示横向)
	.containerRelativeFrame(
		[.horizontall], 
        count: sizeClass == .regular ? 2 : 1, 
        spacing: hSpacing
	)
	.contentMargins(.horzontal, 20.0) // 内容边距
	.safeAreaPadding(.horzontal, 20.0) // 另一种内容边距
	.clipShape(.rect(cornerRadius: 20.0))
	// 滚动过渡
	.scrollTransition(axis: .horizontal) { content, phase in
		content
		.scaleEffect(
			x: phase.isIdentity ? 1.0 : 0.75,
			y: phase.isIdentity ? 1.0 : 0.75
		)
	}
    // 分页(基于屏幕)
    .scrollTargetBehavior(.paging)
    // 分页(基于元素)
    .scollTargetBehavior(.viewAligned)