普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月18日iOS

iOS26适配指南之UIButton

作者 YungFan
2025年8月17日 17:05

介绍

在 iOS 26 中,UIButton 迎来了两项非常实用的更新:

  • Liquid Glass 风格配置方法 — 让按钮拥有全新的半透明折射质感,完美融入 iOS 26 的视觉系统。
  • Symbol 动画切换 — 通过 symbolContentTransition 实现更顺滑的 SF Symbols 切换效果。

适配 Liquid Glass

UIButton.Configuration 增加了符合 Liquid Glass 风格的配置方法:glass()clearGlass()prominentGlass()prominentClearGlass()

代码

import UIKit

class ViewController: UIViewController {
    let configs: [UIButton.Configuration] = {
        [
            {
                // iOS26新增
                var config = UIButton.Configuration.glass()
                config.title = "喜欢"
                config.image = UIImage(systemName: "heart")
                return config
            }(),
            {
                // iOS26新增
                var config = UIButton.Configuration.clearGlass()
                config.title = "收藏"
                config.image = UIImage(systemName: "star")
                return config
            }(),
            {
                // iOS26新增
                var config = UIButton.Configuration.prominentGlass()
                config.title = "分享"
                config.image = UIImage(systemName: "square.and.arrow.up")
                return config
            }(),
            {
                // iOS26新增
                var config = UIButton.Configuration.prominentClearGlass()
                config.title = "下载"
                config.image = UIImage(systemName: "arrow.down.circle")
                return config
            }()
        ]
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemGray

        for (index, config) in configs.enumerated() {
            let button = UIButton(frame: CGRect(x: 120, y: 220 + CGFloat(index) * 70, width: 160, height: 60))
            button.configuration = config
            view.addSubview(button)
        }
    }
}

效果

按钮样式.png

symbolContentTransition

UIButton.Configuration 增加了类型UISymbolContentTransition?的属性symbolContentTransition,用于切换 SF Symbols 图标,并且可以呈现切换动画。

代码

import UIKit

class ViewController: UIViewController {
    let button = UIButton(frame: CGRect(x: 100, y: 200, width: 200, height: 200))

    override func viewDidLoad() {
        super.viewDidLoad()

        var config = UIButton.Configuration.plain()
        config.image = UIImage(systemName: "heart.circle")
        config.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 50, weight: .thin)
        // iOS26新增
        config.symbolContentTransition = UISymbolContentTransition(.replace, options: .speed(0.1))
        button.configuration = config
        button.isSymbolAnimationEnabled = true
        button.tintColor = .systemRed
        view.addSubview(button)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        button.configuration?.image = UIImage(systemName: "heart.circle.fill")
    }
}

效果

切换图标.gif

LLVM integrated assembler: Improving sections and symbols

作者 MaskRay
2025年8月17日 15:00

In my previous post, LLVMintegrated assembler: Improving expressions and relocations delvedinto enhancements made to LLVM's expression resolving and relocationgeneration. This post covers recent refinements to MC, focusing onsections and symbols.

Sections

1
2
3
4
5
6
7
8
9
10
11
12
class MCSection {
...
enum SectionVariant {
SV_COFF = 0,
SV_ELF,
SV_GOFF,
SV_MachO,
SV_Wasm,
SV_XCOFF,
SV_SPIRV,
SV_DXContainer,
};

Previously, the MCSection class used an enum calledSectionVariant to differentiate between various object fileformats, such as ELF, Mach-O, and COFF. These subclasses are used incontexts where the section type is known at compile-time, such as inMCStreamer and MCObjectTargetWriter. Thischange eliminates the need for runtime type information (RTTI) checks,simplifying the codebase and improving efficiency.

Additionally, the storage for fragments' fixups (adjustments toaddresses and offsets) has been moved into the MCSectionclass.

Symbols

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MCSymbol {
protected:
/// The kind of the symbol. If it is any value other than unset then this
/// class is actually one of the appropriate subclasses of MCSymbol.
enum SymbolKind {
SymbolKindUnset,
SymbolKindCOFF,
SymbolKindELF,
SymbolKindGOFF,
SymbolKindMachO,
SymbolKindWasm,
SymbolKindXCOFF,
};

/// A symbol can contain an Offset, or Value, or be Common, but never more
/// than one of these.
enum Contents : uint8_t {
SymContentsUnset,
SymContentsOffset,
SymContentsVariable,
SymContentsCommon,
SymContentsTargetCommon, // Index stores the section index
};

Similar to sections, the MCSymbol class also used adiscriminator enum, SymbolKind, to distinguish between object fileformats. This enum has also been removed.

Furthermore, the MCSymbol class had anenum Contents to specify the kind of symbol. This name wasa bit confusing, so it has been renamedto enum Kind for clarity.

A special enumerator, SymContentsTargetCommon, which wasused by AMDGPU for a specific type of common symbol, has also been removed.The functionality it provided is now handled by updatingELFObjectWriter to respect the symbol's section index(SHN_AMDGPU_LDS for this special AMDGPU symbol).

The previous blog post LLVMintegrated assembler: Improving expressions and relocationsdescribes other changes:

  • The MCSymbol::IsUsed flag was a workaround fordetecting a subset of invalid reassignments and is removed.
  • The MCSymbol::IsResolving flag is added to detectcyclic dependencies of equated symbols.

WWDC 2025 Build a SwiftUI app with the new design

2025年8月17日 12:27

Build a SwiftUI app with the new design

WWDC 2025推出了liquid glass这种重磅级的新设计,怎样在swiftUI开发中用上这些新特性,给你的用户带来耳目一新的视觉盛宴呢,今天我们来聊一下

总结起来就是这个 session 展示了如何用 Liquid Glass 与新 SwiftUI API 重塑 App 结构(导航、标签页、工具栏、搜索、控件),在 iOS 26 和 macOS Tahoe 上实现更轻盈、动态、统一的用户体验。

具体来讲就是以Liquid Glass为中心,在不同场景包括NavigationSplitView, Inspector, TabView, Sheets, Toolbar和不同控件按钮、滑块、菜单的结合,呈现新的设计效果

1. 新设计的核心理念

  • Liquid Glass 是 iOS 26 和 macOS Tahoe 的全新自适应材质,用于 控制和导航元素
  • 它结合玻璃的光学特性和液体的流动感,创造出轻盈、动态的视觉效果,帮助内容成为界面的主角。
  • 在交互时(比如切换、滑动、点击),控件会“活起来”,提供流体化的反馈。

2. 应用结构更新

  • NavigationSplitView:侧边栏采用 Liquid Glass,浮动在内容上,配合新的 backgroundExtensionEffect 可避免图片被裁剪。

  • Inspector:对比侧边栏,采用更细腻的分层效果,与所选内容建立视觉联系。

  • TabView

    • 新的 Tab Bar 可 悬浮在内容之上,并通过 tabBarMinimizeBehavior 设置滚动时的折叠/展开行为。
    • tabViewBottomAccessory 允许在 Tab Bar 下方附加额外控件(如播放控件)。
  • Sheets

    • 默认带有 Liquid Glass 背景。
    • 支持 部分高度,边缘自动与屏幕圆角对齐。
    • 转换到全屏时会逐渐过渡为不透明背景。
    • 新的 导航缩放过渡 让 Sheet/对话框看起来从按钮“流出”。

3. 工具栏 (Toolbars)

  • 工具栏表面为 Liquid Glass,自适应底层内容。
  • 项目可自动分组,也可用 ToolbarSpacer 控制分组间距。
  • badge 修饰符为工具栏按钮提供消息提醒。
  • sharedBackgroundVisibility 可以将项目单独分组,避免背景混淆。
  • 图标更偏向单色,减少干扰;但仍可通过 tint 传达语义。
  • 滚动边缘效果 (scrollEdgeEffect) 自动处理内容与浮动控件之间的可读性,可通过 scrollEdgeEffectStyle 调整。

4. 搜索体验

  • 提供 两大模式

    1. 工具栏内搜索:iPhone 显示在底部,iPad/Mac 显示在右上角。
    2. 独立的搜索页面:可在 TabView 中定义 search 角色。
  • 新的 searchToolbarBehavior API 可精细控制搜索的折叠/展开方式。

  • 搜索框本身置于 Liquid Glass 表面,激活时与内容保持自然过渡。


5. 标准控件的更新

  • 按钮 (Buttons)

    • 默认采用 胶囊形状,与系统圆角保持一致。
    • 新增 extra-large 尺寸,强调主操作。
    • 新的 glassglassProminent 样式将 Liquid Glass 引入按钮。
  • 滑块 (Sliders)

    • 支持 刻度 (tick marks) ,可自动或手动配置。
    • neutralValue 参数允许定义中点值(如播放速度调节)。
  • 菜单 (Menus)

    • 图标统一在左侧,iOS 与 macOS 行为一致。
  • 一致性特性

    • 新的 corner concentricity 概念:控件自动与容器(如 Sheet)保持圆角同心。

6. 自定义 Liquid Glass

  • 使用 glassEffect 修饰符为自定义控件加上 Liquid Glass。
  • 通过 GlassEffectContainer 管理多个玻璃元素,确保光影一致。
  • 使用 glassEffectIDnamespace 配合,可以实现 流体化的玻璃转场(比如徽章展开/收缩)。
  • interactive 修饰符让自定义玻璃控件具备触摸反馈(缩放、弹性、闪光)。
  • 支持 tint 高亮关键控件,但应避免过度装饰。

7. 最佳实践与采纳策略

  • 使用 Xcode 26 SDK 构建,许多 UI 更新自动获得。
  • 检查 App 结构,移除不必要的背景,避免与系统滚动边缘效果冲突。
  • 优先使用系统控件 来获得一致性和自动适配。
  • 在必要时才引入自定义 Liquid Glass 元素,让 App 的独特之处得到凸显。

SwiftUI 劝退实录:AI 都无能为力,你敢用吗?

2025年8月16日 18:39

我完全不理解为什么 SwiftUI 要支持这种写法:让一个 view 去调用自己根本不存在的属性,还能“生效”。比如 .navigationTitle ——本来应该是页面级别的东西,结果你随便在一个 TextButton 上写,它都能改导航栏标题。 但哪个生效?完全不确定。
我写了段hello world 级别的代码,任何子 view 都能 .navigationTitle("xxx"),你猜猜最终会显示什么?
我问了好几个 AI,全军覆没,一个都没答对。

struct AddView: View {
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {}, label: {
                    Text("button")
                        .foregroundColor(.white)
                        .frame(height: 30)
                        .frame(maxWidth: .infinity)
                        .background(Color.accentColor)
                        .cornerRadius(10)
                        .navigationTitle("title1")
                })
                .navigationTitle("title2")
                
                Text("text")
                    .navigationTitle("title3")
            }.padding(15)
            .navigationTitle("title4")
        }.navigationTitle("title5")
    }
}
#Preview {
    NavigationStack{
        AddView()
    }
}

好了,揭示结果 image.png

最终的 title 如图所示,显示的是 title1

更离谱的是,这还不是个例。不光是 .navigationTitle,很多 SwiftUI 的 写法 都存在类似问题:表面上修饰局部,其实是全局冒泡,导致歧义、结果不直观,维护起来还埋坑。

这种设计到底是为了所谓的“灵活”,还是偷懒的语法糖?无论如何,带来的体验只有一个词能形容:迷惑。
这就好比一家商场里的小铺子,居然有权力要求改整个商场大楼的名字。
你说合理不合理?

或许有人会说:“你别这样写不就行了?” 但问题是 —— SwiftUI 本来就允许这样写。项目一旦复杂起来,或者多人合作,甚至是接手别人代码的时候,你根本无法确保没人这么用。一旦遇到问题,调试起来极其困难。

而且最最最大的问题是:AI 根本帮不了你。 都 2025 年了,这么一小段 SwiftUI 代码,AI 仍然无法稳定分析对错。你敢想?别人写其他代码的遇到问题 AI 一补全,分分钟下班。你写 SwiftUI,遇到个小 bug,谁都帮不了你 —— AI 不行,群里的老哥大多数也不懂 SwiftUI,最后只能自己一个人摸黑查,个人小项目无所谓,公司项目 你不完全摸清你能下班?

你以为的 SwiftUI 是「比 UIKit 少写 30%-50% 代码,开发效率高很多」,实际体验却是:2 小时飞机 vs 4 小时高铁。 飞机看似快,但你得提前很久到机场,机场又远在郊区,安检、候机、延误……加起来,可能还不如稳稳当当坐高铁四小时。2 小时飞机 只有对比 8 小时以上高铁 才算是有优势。 SwiftUI 就是这样 ——看似优雅省事,实际暗坑重重,会更折腾。

我也真不懂,为什么会有人劝新人入坑就直接学 SwiftUI,还能一脸轻松地说“SwiftUI 简单”。说简单的,大概率都是已经熟悉过 UIKit、或者其他端老程序员吧。 你确定一个新人能看懂 SwiftUI 这一堆花里胡哨的语法糖?

更现实的是,只学 SwiftUI 能找到工作的概率基本为 0。就算运气好进了公司,接手的项目十有八九还是 UIKit 的。所以如果是以找工作为目的入坑 iOS,优先建议学 UIKit,SwiftUI 顶多是锦上添花。

别的不说,光是游戏开发,ios 端的工程几乎和 swiftui 无关,甚至还是 Objective-C 的老代码,这个绕不过去。

下面是 ai 阵亡案发现场

GPT5

image.png

Gemini

image.png

QWEN

image.png

Deepseek

image.png

昨天 — 2025年8月17日iOS

老司机 iOS 周报 #347 | 2025-08-18

作者 ChengzhiHuang
2025年8月17日 19:39

ios-weekly
老司机 iOS 周报,只为你呈现有价值的信息。

你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。

文章

🐕 万字长文解码如何玩转Prompt(附实践应用)

@zhanggferry:这是一篇来自淘宝团队,讲解提示词工程(PE)非常全面的文章,包含业界各类优秀方法论,当你苦于 AI 「不听话」时可以从这里获取灵感。除了提示词设计的七个核心原则,还可以结合你的应用场景,考虑使用这六大高级技巧:

  • 思维链:提供「推理过程 + 答案」范例,激发 LLM 深度逻辑思考
  • 零样本思维链:无需范例,仅追加 “让我们一步一步地思考”,触发 LLM 分步推理
  • 自我一致性:调高模型温度,多次独立解答同一问题,按 “少数服从多数” 选最终答案
  • ReAct(思考与行动):以 “思考→行动→观察” 循环,让 LLM 调用外部工具(API、搜索引擎等)
  • 生成知识提示:先让 LLM 生成问题相关背景知识,再结合知识回答原问题
  • 结构化提示:用 XML、JSON、YAML 等严格格式定义提示词,消除歧义

🐕 @isolated(any)

@Smallfly:在 Swift 并发模型逐渐完善的过程中,@isolated(any) 作为一个相对低调却关键的特性,正在为 API 设计与任务调度提供新的可能性。

这篇文章系统地阐释了 @isolated(any) 的设计动机、语义价值以及在实际 API 中的应用方式。作者通过对比 GCD 与 Swift Concurrency,结合异步函数和 actor 隔离的案例,清晰展示了该属性如何让调度系统基于隔离信息做出更合理的决策。

对于大多数开发者而言,@isolated(any) 在日常编码中可能并不直接显现;但若你正在编写并发 API、涉及跨 actor 调度,或希望深入理解 Swift 并发体系的演进方向,这篇文章将为你提供扎实的理论支撑与实践启发。

🐎 Flutter 3.35 发布,快来看看有什么更新吧

@david-clang:这次更新除了常规的控件更新和性能优化外,还有两点值得关注下:

  1. 多窗口支持:在 Engine 层已实现在 Windows 和 macOS 中创建和更新窗口的基础逻辑 ( #168728 ),后续版本将更新 Linux 系统,并引入实验性 API 以支持多窗口功能。

    • 虽然官方还没提供多窗口 Demo 来展示 API 的使用,但社区已有人提交 Windows Demo 的PR,也可以通过 Multi Window 了解官方的进度。
  2. Dart & Flutter MCP:Dart 和 Flutter MCP Server 正式 stable 发布,主要是增强了 AI 编码助手的 Dart 和 Flutter 上下文,Dart 和 Flutter MCP Server 充当桥梁,可以让 AI 通过 Dart 和 Flutter 工具链访问项目的更多上下文:

    • 修复运行时错误 :检查实时 Widget 树,识别 Flutter RenderFlex 溢出,并自动应用正确的修复。
    • 管理依赖项 :在 pub.dev 上找到针对特定任务的最佳包,将其添加到 pubspec.yaml ,然后运行 pub get
    • 编写和纠正代码 :为新功能生成样板,然后自行纠正其在此过程中引入的任何分析错误。

代码

🐕 swiftlang/swift-subprocess: Subprocess is a cross-platform package for spawning processes in Swift.

@BarneySwift Subprocess 是 Apple 官方提供的一个用于在 Swift 中执行外部进程的现代化库。它提供了类型安全、结构化并发的 API 来启动和管理子进程,支持灵活的输入输出重定向、环境变量配置和进程控制。相比传统的 Process/NSTask API,它采用了 Swift 并发模型,让异步执行和错误处理更加优雅。该库是 Swift 生态系统中处理外部命令执行的标准解决方案。

🐎 Define the scroll edge effect style of a scroll view for Liquid Glass

@AidenRao:在 OS 26 的液态玻璃适配中,需要借助 OS 26 中引入的 scrollEdgeEffectStyle(_:for:) 方法,去控制滚动视图在边缘区域的样式,尤其是在与安全区域或系统 UI(如标签栏)重叠部分的视觉体验。

内推

重新开始更新「iOS 靠谱内推专题」,整理了最近明确在招人的岗位,供大家参考

具体信息请移步:https://www.yuque.com/iosalliance/article/bhutav 进行查看(如有招聘需求请联系 iTDriverr)

关注我们

我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。

关注有礼,关注【老司机技术周报】,回复「2024」,领取 2024 及往年内参

同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom

说明

🚧 表示需某工具,🌟 表示编辑推荐

预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)

昨天以前iOS

Flutter 实现类似抖音/TikTok 的竖向滑动短视频播放器

作者 Daniel02
2025年8月16日 00:23

近年来,短视频平台(如抖音、TikTok)已经成为主流的内容消费方式。本文将分享一个用 Flutter 实现的 竖向滑动短视频播放器,支持自动播放、滑动切换、视频信息展示等核心功能。


功能概述

  • 竖向 PageView 滑动切换视频
  • 自动播放当前视频,暂停非当前视频
  • 视频信息展示(作者头像、昵称、标题、音乐信息等)
  • 视频互动按钮(点赞、评论、转发等)
  • 黑色沉浸式 UI

效果类似 TikTok/抖音。


项目结构

lib/
  app/                 # 应用入口
  packages/app_ui/     # 全局主题样式
  reels/               # 短视频模块
    bloc/              # 状态管理(Bloc)
    model/             # 数据模型与仓库
    reel/              # 单条视频展示
    view/              # 视频列表(PageView)

1. 应用入口与主题

入口 main.dart

import 'package:reel_views/app/app.dart';
import 'package:reel_views/bootstrap.dart';

void main() {
  bootstrap(() => const App());
}

应用 App 中使用 Bloc 提供 PostsRepositoryReelBloc,并应用自定义暗色主题 AppDarkTheme

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return RepositoryProvider(
      create: (context) => PostsRepository(),
      child: MaterialApp(
        theme: const AppDarkTheme().theme,
        localizationsDelegates: AppLocalizations.localizationsDelegates,
        supportedLocales: AppLocalizations.supportedLocales,
        home: BlocProvider(
          create: (context) => ReelBloc(
            postsRepository: context.read<PostsRepository>()
          ),
          child: const ReelsView(),
        ),
      ),
    );
  }
}

2. 数据模型与仓库

短视频数据来自 PostsRepository

class PostsRepository {
  static final recommendedReels = [
    PostReelBlock(
      author: PostAuthor.randomConfirmed(),
      id: "1",
      caption: "送你一朵小红花",
      media: 'assets/video/Butterfly-209.mp4'
    ),
    ...
  ];
}

作者信息用 PostAuthor 封装,并支持随机生成测试数据:

class PostAuthor {
  const PostAuthor({
    required this.id,
    required this.avatarUrl,
    required this.username,
    this.isConfirmed = false
  });

  factory PostAuthor.randomConfirmed() {
    final randomUser = _confirmedUsers[Random().nextInt(_confirmedUsers.length)];
    return PostAuthor(
      id: randomUser.id,
      username: randomUser.username!,
      avatarUrl: randomUser.avatarUrl!,
      isConfirmed: true,
    );
  }
}

3. 状态管理(Bloc)

ReelBloc 负责视频数据加载:

class ReelBloc extends Bloc<ReelEvent, ReelState> {
  ReelBloc({ required PostsRepository postsRepository })
    : _postsRepository = postsRepository,
      super(const ReelState.initial()) {
    on<ReelRecommendedPostsPageRequested>(_onReelRecommendedPostsPageRequested);
  }

  Future<void> _onReelRecommendedPostsPageRequested(
    ReelRecommendedPostsPageRequested event,
    Emitter<ReelState> emit,
  ) async {
    emit(state.loading());
    final recommendedBlocks = [...PostsRepository.recommendedReels..shuffle()];
    emit(state.populated(blocks: recommendedBlocks));
  }
}

4. 视频列表页面(竖向 PageView)

ReelsView 使用 PageView.builder 实现竖向滑动:

class _ReelsViewState extends State<ReelsView> {
  late PageController _pageController;
  late ValueNotifier<int> _currentIndex;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(keepPage: false);
    _currentIndex = ValueNotifier(0);
    context.read<ReelBloc>().add(const ReelRecommendedPostsPageRequested());
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ReelBloc, ReelState>(
      builder: (context, state) {
        final blocks = state.blocks;
        return PageView.builder(
          controller: _pageController,
          scrollDirection: Axis.vertical,
          onPageChanged: (index) => _currentIndex.value = index,
          itemCount: blocks.length,
          itemBuilder: (context, index) {
            final block = blocks[index];
            final isCurrent = index == _currentIndex.value;
            return Reel(
              key: ValueKey(block.id),
              play: isCurrent,
              block: block,
            );
          },
        );
      },
    );
  }
}

5. 单条视频展示

Reel 组件负责渲染单条视频及 UI 叠层:

class Reel extends StatefulWidget {
  const Reel({ required this.block, required this.play, super.key });

  final PostReelBlock block;
  final bool play;

  @override
  State<Reel> createState() => _ReelState();
}

class _ReelState extends State<Reel> {
  late VideoPlayerController _videoController;

  @override
  void initState() {
    super.initState();
    _videoController = VideoPlayerController.asset(widget.block.media);
  }

  @override
  Widget build(BuildContext context) {
    return InlineVideo(
      shouldPlay: widget.play,
      videoPlayerController: _videoController,
      stackedWidget: Stack(
        children: [
          VerticalButtons(widget.block),
          // 这里省略底部作者和标题信息布局
        ],
      ),
    );
  }
}

6. 视频播放器封装

InlineVideovideo_player 进行了封装,实现自动播放/暂停:

class InlineVideo extends StatefulWidget {
  const InlineVideo({
    required this.shouldPlay,
    required this.stackedWidget,
    required this.videoPlayerController,
    super.key
  });

  @override
  State<InlineVideo> createState() => _InlineVideoState();
}

class _InlineVideoState extends State<InlineVideo> {
  late VideoPlayerController _controller;

  @override
  void initState() {
    super.initState();
    _controller = widget.videoPlayerController;
    _controller.initialize().then((_) {
      if (widget.shouldPlay) _controller..play()..setLooping(true);
    });
  }

  @override
  void didUpdateWidget(covariant InlineVideo oldWidget) {
    if (oldWidget.shouldPlay != widget.shouldPlay) {
      widget.shouldPlay ? _controller.play() : _controller.pause();
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AspectRatio(
          aspectRatio: _controller.value.aspectRatio,
          child: VideoPlayer(_controller),
        ),
        widget.stackedWidget
      ],
    );
  }
}

7. 视频交互按钮

右侧互动按钮由 VerticalButtons 实现:

class VerticalButtons extends StatelessWidget {
  const VerticalButtons(this.block, {super.key});
  final PostReelBlock block;

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomRight,
      child: Column(
        children: [
          const VerticalGroup(icon: Icons.favorite_outline, statisticCount: 30),
          VerticalGroup(
            statisticCount: 2,
            child: SvgPicture.asset('assets/icons/chat_circle.svg', width: 30),
          ),
          const VerticalGroup(icon: Icons.more_vert_sharp, withStatistic: false),
          CircleAvatar(backgroundImage: NetworkImage(block.author.avatarUrl)),
        ],
      ),
    );
  }
}

总结与优化方向

本文用 Flutter + Bloc + video_player 实现了一个竖向短视频播放器,具备 TikTok 类似的交互体验。

未来可以优化的方向:

  1. 视频缓存与预加载:减少切换时的延迟
  2. 网络视频支持:支持从 API 拉取视频
  3. 手势交互:双击点赞、长按暂停
  4. 性能优化:更细粒度的资源释放与控制

你可以直接在此基础上添加更多功能,打造属于自己的短视频应用。


源码下载

github.com/wutao23yzd/…

市面上有多少智能体平台

一、大模型智能体平台:扣子(coze)

字节跳动的扣子平台提供了丰富的功能,包括插件、系统、记忆库、工作流等,支持知识库和自定义的插件构建的机器人可以轻松的部署到多个平台,几乎不需要具备编程的基础模型插件。还有知识库等核心技术都已经进行了非常好的封装,支持多agent模式,允许用户创建多个专注于特定任务的单agent,并且可以统一管理。

二、大模型智能体平台:腾讯元器

腾讯元器的开发者可以通过插件、知识库、工作流等方式,快速的、低门槛的来打造高质量的智能体,支持发布到QQ、微信等平台的同时,也支持API的调用。

三、大模型智能体平台:智谱清言

中国版的对话语言模型与GLM大模型进行对话,基于ChatGLM中英双语大模型进行开发,通过万亿字符的文本与代码的预训练,结合有监督的微调技术,具备了通用问答、多轮对话、创意写作、代码生成、虚拟对话、AI画图、文档和图片解读等等的能力。

四、大模型智能体平台:百度灵境

百度灵境智能体支持低代码的开发模式,灵活度相对更高,可以一键分发到微信客服、微信公众号、Web端、H5以及百度灵境矩阵等主流渠道之上。基于这些渠道应用,还能够在百度搜索、百度信息流等主流场景下进行分发与挂载。

SwiftUI TabView 源码解析

作者 Lucklylin
2025年8月13日 23:16

SwiftUI TabView 解析

TabView 是 SwiftUI 中用于创建标签页视图的组件,类似于 UIKit 中的 UITabBarController。下面我将解释源代码并提供使用示例。

源代码解析

基本结构

public struct TabView<SelectionValue, Content> : View 
    where SelectionValue : Hashable, Content : View
  • TabView 是一个泛型结构体,遵循 View 协议
  • SelectionValue 是用于跟踪当前选中标签的值的类型,必须遵循 Hashable 协议
  • Content 是视图内容的类型,必须遵循 View 协议

初始化方法

  1. 旧版初始化方法 (已弃用)
@available(iOS, deprecated: 100000.0, message: "Use TabContentBuilder-based TabView initializers instead")
public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content)
  • 这个初始化方法已被标记为弃用
  • 使用 @ViewBuilder 构建内容
  • 接受一个可选的 Binding<SelectionValue> 用于跟踪当前选中的标签

使用示例

import SwiftUI

struct OldTabViewExample: View {
    @State private var selectedTab: Int = 0

    var body: some View {
        TabView(selection: $selectedTab) { // selection 是可选 Binding
            HomeView()
                .tabItem { Label("首页", systemImage: "house.fill") }
                .tag(0)

            CalendarView()
                .tabItem { Label("日历", systemImage: "calendar") }
                .tag(1)

            SettingsView()
                .tabItem { Label("设置", systemImage: "gearshape") }
                .tag(2)
        }
    }
}
  1. 新版初始化方法 (iOS 18+)
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
public init<C>(selection: Binding<SelectionValue>, @TabContentBuilder<SelectionValue> content: () -> C) 
    where Content == TabContentBuilder<SelectionValue>.Content<C>, C : TabContent
  • 使用新的 @TabContentBuilder 构建内容
  • 需要 iOS 18+ 等新系统版本
  • 提供更好的类型安全性和更简洁的语法

使用示例

import SwiftUI

struct NewTabViewExample: View {
    @State private var selectedTab: Int = 0

    var body: some View {
        if #available(iOS 18.0, *) {
            TabView(selection: $selectedTab) { // 必须有 Binding
                Tab(value: 0) {
                    HomeView()
                }
                .tabItem { Label("首页", systemImage: "house.fill") }

                Tab(value: 1) {
                    CalendarView()
                }
                .tabItem { Label("日历", systemImage: "calendar") }

                Tab(value: 2) {
                    SettingsView()
                }
                .tabItem { Label("设置", systemImage: "gearshape") }
            }
        } else {
            Text("请升级到 iOS 18 使用该写法")
        }
    }
}

扩展方法

  1. 当 SelectionValue 为 Int 时的简化初始化
extension TabView where SelectionValue == Int {
    nonisolated public init(@ViewBuilder content: () -> Content)
}
  • 当标签选择值是 Int 类型时,可以省略 selection 参数

这也就是最常见的使用方法了

示例

import SwiftUI

struct IntTabViewExample: View {
    var body: some View {
        TabView { // 没有 selection
            HomeView()
                .tabItem { Label("首页", systemImage: "house.fill") }
                .tag(0)

            CalendarView()
                .tabItem { Label("日历", systemImage: "calendar") }
                .tag(1)

            SettingsView()
                .tabItem { Label("设置", systemImage: "gearshape") }
                .tag(2)
        }
    }
}
  1. 当 SelectionValue 为 Never 时的初始化 (iOS 18+)
extension TabView {
    public init<C>(@TabContentBuilder<Never> content: () -> C) 
        where SelectionValue == Never, Content == TabContentBuilder<Never>.Content<C>, C : TabContent
}
  • 用于不需要选择功能的标签视图
  • 需要 iOS 18+ 等新系统版本

示例

struct StaticTabView: View {
    var body: some View {
        TabView {
            Tab("Home") {
                Text("Home Content")
            }
            
            Tab("About") {
                Text("About Content")
            }
        }
    }
}

更多的使用示例

基础示例 (iOS 13+ 兼容)

struct BasicTabView: View {
    @State private var selectedTab = 0
    
    var body: some View {
        TabView(selection: $selectedTab) {
            Text("First Tab")
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
                }
                .tag(0)
            
            Text("Second Tab")
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
                }
                .tag(1)
        }
    }
}

简化版 (当 SelectionValue 是 Int)

struct SimpleTabView: View {
    var body: some View {
        TabView {
            Text("Home")
                .tabItem {
                    Label("Home", systemImage: "house")
                }
            
            Text("Settings")
                .tabItem {
                    Label("Settings", systemImage: "gear")
                }
        }
    }
}

iOS 18+ 新 API 示例

@available(iOS 18.0, macOS 15.0, *)
struct NewTabViewExample: View {
    @State private var selectedTab: String = "home"
    
    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("Home", value: "home") {
                Text("Home Content")
            }
            
            Tab("Profile", value: "profile") {
                Text("Profile Content")
            }
            
            Tab("Settings", value: "settings") {
                Text("Settings Content")
            }
        }
    }
}

不需要选择功能的标签视图 (iOS 18+)

@available(iOS 18.0, macOS 15.0, *)
struct StaticTabView: View {
    var body: some View {
        TabView {
            Tab("Home") {
                Text("Home Content")
            }
            
            Tab("About") {
                Text("About Content")
            }
        }
    }
}

tabItem

另外tabItem的作用是布局一个视图作为标签页元素,所有可以向其中加入视图,包括允许你指定每个标签页在标签栏中显示的图标和标题。

tabItem 闭包中,你通常会提供一个 Label、Image 或 Text 视图,或者它们的组合。

主要特性

  • 必须与 TabView 一起使用:tabItem 只能应用于 TabView 的子视图
  • 需要配合 tag 修饰符使用:如果你需要跟踪当前选中的标签
  • 自动适应外观:系统会根据当前环境自动调整标签项的外观

高级用法,动态改变标签项

.tabItem {
    Label(showDetails ? "Detailed" : "Simple", 
          systemImage: showDetails ? "info.circle.fill" : "info.circle")
}

总结

  • TabView 是 SwiftUI 中创建标签页界面的主要组件
  • 旧版 API 使用 tabItemtag 修饰符定义标签
  • iOS 18+ 引入了新的 Tab 视图和 @TabContentBuilder,提供了更简洁的语法
  • 根据是否需要跟踪当前选中的标签,可以选择不同的初始化方法
  • 当选择值是 Int 类型时,可以使用简化版的初始化方法

新的 iOS 18 API 提供了更直观的方式来定义标签视图,但如果你需要支持旧版系统,仍然需要使用传统的 tabItem 方法。

本文使用 「Markdown 在线编辑器 | 公众号内容排版工具」 排版

如何使用和自定义 SwiftUI 标签

作者 Lucklylin
2025年8月13日 22:19

1 如何在 SwiftUI 中添加标签?

SwiftUI 中的 Label 组件结合了标题和可选的系统图像,使其成为适用于各种 UI 元素的多功能工具。要创建标签,可以使用以下语法:

Label("Home", systemImage: "house")

第一个参数是标签的标题,可以是简单字符串,也可以是用于国际化的本地化字符串。第二个参数 systemImage 允许您从 SF Symbols 集合中添加图标到标签。

2 如何更改标签的大小

您还可以使用字体修饰符自定义标签的大小。例如,要使标签更大,可以使用标题字体:

这将增加标签标题和图标的尺寸。如果要独立于文本放大或缩小图像,可以使用图像比例修改器:

    Label("Swift", systemImage: "swift")
        .imageScale(.small)
    
    Label("Swift", systemImage: "swift")
        .imageScale(.medium)
    
    Label("Swift", systemImage: "swift")
        .imageScale(.large)

SwiftUI 标签就像文本一样,会响应动态字体大小。图标和文本可以完美地一起缩放:

SwiftUI 标签适应动态类型大小

SwiftUI 标签适应动态类型大小

3 如何更改 SwiftUI Label 的颜色

    Label("Add New Folder", 
          systemImage: "folder.badge.plus")
    
    Label("Add New Folder", 
          systemImage: "folder.badge.plus")
        .foregroundStyle(.accent)
    
    Label("Add New Folder", 
          systemImage: "folder.badge.plus")
        .foregroundStyle(.blue, .pink)

Swiftui 标签与前景样式改变颜色

Swiftui 标签与前景样式改变颜色

如果要更改列表内标签的图标颜色,则应使用色调修饰符而不是前景样式:

    struct LabelListExampleView: View {
        var body: some View {
            NavigationStack {
                List {
                    Label("Folder 1", systemImage: "folder.fill")
                    Label("Folder 2", systemImage: "folder.fill")
                    Label("Folder 3", systemImage: "folder.fill")
                    Label("New Folder", systemImage: "folder.badge.plus")
                }
                .navigationTitle("Labels")
               //  .foregroundStyle(.orange)
               //  .foregroundStyle(.pink, .blue)
                .tint(.cyan)
            }
        }
    }

.tint(.cyan)会将视图的主色调修改为青色。

4 将样式应用于标签

除了基本的自定义选项外,SwiftUI 还提供了各种可应用于标签的标签样式。这些样式定义了标签的外观和行为,例如,它是仅显示标题、仅显示图标,还是两者兼而有之。

要应用标签样式,可以使用 labelStyle修饰符。例如,要创建仅包含图标的标签,可以使用:

    Label("Home", systemImage: "house")
        .labelStyle(IconOnlyLabelStyle())
        
    Label("Home", systemImage: "house")
         .labelStyle(.iconOnly)    // for iOS 14+, macOS 11+

这将显示仅带有房屋图标的标签,并删除标题。

您还可以选择始终使用标题和图标或仅使用标题:

    Label("Home", systemImage: "house")
         .labelStyle(.titleAndIcon)
         
    Label("Home", systemImage: "house")
         .labelStyle(.titleOnly)

5 通过采用 LabelStyle 协议创建自定义标签样式

您还可以通过采用 LabelStyle 协议来创建自定义标签样式。这允许您创建与您的应用设计和品牌相匹配的独特标签样式。

    extension LabelStyle where Self == SocialFeedTagLabelStyle {
        static var socialFeedTag: SocialFeedTagLabelStyle {
            SocialFeedTagLabelStyle()
        }
    }
    
    struct SocialFeedTagLabelStyle: LabelStyle {
        @ScaledMetric(relativeTo: .footnote) private var iconWidth = 14.0
        
        func makeBody(configuration: Configuration) -> some View {
            HStack {
                configuration.icon
                    .foregroundColor(.secondary)
                    .frame(width: iconWidth)
                configuration.title
            }
            .padding(6)
            .background(in: RoundedRectangle(cornerRadius: 5, style: .continuous))
            .compositingGroup()
            .shadow(radius: 1)
            .font(.caption)
        }
    }

makeBody(configuration:) 是 SwiftUI 样式协议(如 LabelStyle、ButtonStyle、ToggleStyle 等) 的核心方法,它的作用是:

接收组件的配置(configuration)(比如 Label 的图标和文本) 返回一个自定义的 View,决定如何渲染该组件

结构体实现了 LabelStyle 协议,定义了标签的自定义外观:

@ScaledMetric: 这是一个属性包装器,它会根据用户的字体大小设置自动调整 iconWidth 的值。relativeTo: .footnote 表示缩放是基于脚注文本大小的。 makeBody 方法: 定义了标签的布局和样式:

使用 HStack 水平排列图标和标题

configuration.icon: 标签的图标部分,设置为次要颜色,并限制宽度为 iconWidth configuration.title: 标签的文本部分

扩展LabelStyle协议,添加一个计算属性是官方推荐的方法,静态属性直接实例化这个结构体,在使用时就不用实例化,而是使用.socialFeedTag的书写方式。如果不扩展,那么就像这样使用。

Label("标签文字", systemImage: "tag")
    .labelStyle(SocialFeedTagLabelStyle())  // 显式创建样式实例

然后,您可以将此标签样式应用于任何标签。例如,我将其用于流式布局中的标签列表:

    FlowLayout(alignment: .leading) {
        ForEach(tags) { tag in
            Label(tag.title, systemImage: tag.icon)
                .labelStyle(.socialFeedTag)
        }
    }

结论

SwiftUI 中的 Label 组件是一款功能强大的工具,可用于向用户界面添加描述性信息。它能够组合标题和图标,并提供各种自定义选项,让您可以创建视觉上引人入胜且信息丰富的标签,从而提升用户体验。

本文使用 「Markdown 在线编辑器 | 公众号内容排版工具」 排版

UIScene in iOS

作者 songgeb
2025年8月13日 15:45

UIScene是iOS 13引入的,核心要解决的是,原来的基于1个Window管理App UI的策略,不能很好的适配像iPad中出现的一个App对应多个Window的场景

iOS 13之前

在iOS 13以前,App启动后执行的方法以及AppDelegate所负责的工作如下图所示

wechat_2025-08-13_152124_019.png

  • App Delegate一方面要负责App进程生命周期方法执行、事件回调
  • 另一方面还要处理UI的生命周期

iOS 13开始

从iOS 13开始,变化如下:

wechat_2025-08-13_152245_306.png

  • 关于App进程生命周期,还是App Delegate在负责,保持不变
  • 但UI生命周期的管理则是交给了Scene Delegate

这也就引出了Scene、Scene Delegate、UIWindowScene等概念。最核心的理念在于:

原来是一个App进程对应一个UI场景;现在开始,一个App进程将对应多个UI场景,其中的每一个场景都叫做Scene

既然引入了场景Scene,那场景所对应的实例(UISceneUIWindowScene)、代理(Scene Delegate)、场景管理的window等概念也就非常合理了

对于上图,还有几点需要关注:

  1. App Delegate中负责创建、切换Scene
  2. 当实现Scene Delegate中的各种UI回调后,原来App Delegate的UI回调变不再执行
  3. 由于一个App进程可能有多个Scene,所以App Delegate中也会有Scene创建、连接、断开连接、销毁等逻辑,这一点在后面的图中更能看出来

引入Scene后的App启动流程

wechat_2025-08-13_152333_410.png

wechat_2025-08-13_152345_160.png

Q&A

1. iPhone是否支持多个Scene

不支持

尽管官方文档中说iOS支持,但通过UIApplication.shared.supportsMultipleScenes结果来看,iOS应用并不支持

2. 一个Scene是只对应一个Window吗

  • 不是的,可以对应多个Window,可以通过APIUIWindowScene.windows了解到
  • 尽管如此,大部分情况下一个Scene只有1个Window

3. 如何初始化Scene

开发者不要直接创建UIWindowScene实例,而是交给系统创建,创建方式有如下几种:

  • 通过在Info.plist中scene配置中指定scene的类名
  • 或者在app delegate的 application(_:configurationForConnecting:options:)方法中,创建UISceneConfiguration时指定scene的类名
  • 也可以通过嗲用UIApplication.requestSceneSessionActivation(_:userActivity:options:errorHandler:)方法来获取/创建scene实例

参考

苹果审核被拒要听劝,能沟通回复解决真的不用改!

作者 iOS研究院
2025年8月13日 09:50

背景

由于苹果审核最后一步是人工介入,所以这也大大增加了影响审核结果的不确定。

比如最近有一个同行,本来是iPhone设备的产品,应是被苹果因为不适配iPad被打回。

Guideline 4.0 - Design  
  
Several screens of the app were crowded, laid out, or displayed in a way that made it difficult to use the app when reviewed on iPad Air 11-inch (M2) running iPadOS 18.6.  
  
Next Steps  
  
To resolve this issue, revise the app to ensure that the content and controls on the screen are easy to read and interact with.  
  
Note that users expect apps they download to function on all the devices where they are available. Since your app may be downloaded onto iPad devices, it is important that it also function as expected for iPad users.

简单来说:

苹果认为该应用会被安装在iPad类型的设备中,期望UI可适配iPad上的所有情况。

但,离谱的是苹果只是提出页面不适配,感到了拥挤并没有明确说明,具体是哪些页面。

正常来讲附件会有对应的应用截图,实际上并没有任何参考。

自查阶段

  1. 构建勾选情况,如果勾选了iPad构建的版本,除了本身要兼容iPhone还必须要兼容iPad,而且在市场截图还必须对应配置。

构建版本.png

  1. 使用iPad自查页面,确保产品本身确实没有审核员所说的适配问题

快速解决方案

由于该同行仅仅只是勾选了iPhone尺寸,并不会做iPad的兼容。同时,该产品已经顺利迭代过3~4次,并非首次提交。所以,用最简单直接的方式向苹果主动说明并且做一个保证

回复内容:
苹果审核:
   你好,感谢你的提醒。我们的产品并没有iPad建构的规划,并且我们保证在后续的迭代中,都并不会为
iPad的用户提供服务。我们在之前的迭代中都没有遇到此类问题,并且我们确保在项目构建未勾选iPad相关
配置,所以我们不清楚为什么一定适配iPad所有场景?
最好的问候

对话截图.jpeg

遵守规则,方得长治久安,最后祝大家大吉大利,今晚过审!

相关推荐

# Pingpong和连连的平替,让AppStore收款无需新增持有人。

# 苹果加急审核是“绿色通道”还是“死亡陷阱”?

# 苹果开发者邮箱,突然收到11.2通知严重么?

# 不想被苹果卡审最好错开这两个提审时间

# 手撕苹果审核4.3是代码问题还是设计问题?

# 有幸和Appstore审核人员进行了一场视频会议特此记录。

知识星球

更多Appstore咨询问题,请关注知识星球。「提供1v1上架指导,帮助开发者解决Appstore的疑难杂症,助力每一位开发者!」

腾讯元器的优点是什么

元器本身的优点有三个:

1、团队功能,一个团队可以有50人,支持创建5个。方便小企业和团队,在元器上以团队方式创建智能体、工作流、插件、知识库,并共享这些资产。也方便大家共同协助,相互学习,我创立了一个”浪洋洋和朋友们”的团队,欢迎大家加入。

2、有简单的官方说明文档,能在元器平台内直接加入官方群,有负责人解答。

3、免费TOKEN额度高,估计是用的人少,现在单个账号免费额度有1个亿,轻松实现人生小目标,用不完真的用不完。扣子的免费TOKEN额度很少了,初学者练手可以试试元器。

drawRect方法的理解

2025年8月12日 14:06

drawRect: 是 UIView 中用于自定义绘制内容的核心方法,对iOS开发者来说,想要高效绘图,我们需要深入理解这个方法。

一、基础调用时机

1. 首次显示视图时

  • 当视图被添加到视图层级时
  • 当视图的 hidden 属性从 YES 变为 NO 时
  • 当视图从父视图的 nil 变为非 nil 时

2. 视图尺寸变化时

  • 当视图的 frame 或 bounds 属性改变时
  • 设备旋转导致视图尺寸变化时
  • 父视图布局改变导致子视图尺寸变化时

3. 显式请求重绘时

  • 调用 setNeedsDisplay 方法
  • 调用 setNeedsDisplayInRect: 方法(部分重绘)

二、详细调用场景分析

1. 自动调用场景

class CustomView: UIView {
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        // 绘制代码
        print("drawRect called with rect: (rect)")
    }
}

// 以下操作会自动触发 drawRect:
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
window.addSubview(view) // 首次显示触发

view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) // 尺寸变化触发

view.isHidden = true
view.isHidden = false // 显示状态变化触发

2. 手动触发场景

// 标记整个视图需要重绘
view.setNeedsDisplay()

// 标记视图的特定区域需要重绘
view.setNeedsDisplay(CGRect(x: 10, y: 10, width: 50, height: 50))

三、调用机制原理

1. 系统绘制周期

  1. RunLoop 的 BeforeWaiting 阶段:系统检查所有标记为需要重绘的视图
  2. 合并绘制请求:将多个 setNeedsDisplay 调用合并为一次绘制
  3. 调用顺序:按照视图层级从父视图到子视图依次调用

2. 性能优化机制

  • 延迟合并:系统不会立即响应每次属性变化,而是在下一个绘制周期统一处理
  • 脏矩形技术:只重绘发生变化的部分区域(通过 rect 参数传递)

四、重要注意事项

1. 不要直接调用 drawRect:

// 错误做法 ❌
view.draw(CGRect.zero)

// 正确做法 ✅
view.setNeedsDisplay()

2. 绘制性能影响

  • 频繁调用 drawRect: 会严重影响性能
  • 复杂绘制应考虑使用 CAShapeLayer 或 Core Graphics 离屏渲染

3. 背景色与绘制

  • 设置 backgroundColor 不会触发 drawRect:
  • 如果自定义了 drawRect:,背景色需要在方法内手动绘制

五、高级调用场景

1. 内容模式影响

view.contentMode = .redraw // 尺寸变化时自动调用 drawRect:
view.contentMode = .scaleToFill // 默认模式,不自动触发重绘

2. 动画中的调用

UIView.animate(withDuration: 1.0) {
    view.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
    // 动画过程中会多次调用 drawRect:
}

3. 滚动视图中的调用

scrollView.didScroll {
    // 滚动时频繁调用 setNeedsDisplay
    visibleCells.forEach { $0.setNeedsDisplay() }
}

六、实践建议

1. 减少不必要的重绘

// 使用局部重绘
func updatePartialContent() {
    let dirtyRect = CGRect(x: 10, y: 10, width: 50, height: 50)
    setNeedsDisplay(dirtyRect)
}

2. 复杂绘制优化

// 使用 display link 控制绘制频率
let displayLink = CADisplayLink(target: self, selector: #selector(updateDrawing))
displayLink.preferredFramesPerSecond = 30 // 限制为30FPS
displayLink.add(to: .current, forMode: .common)

3. 离屏渲染技术

// 在后台线程创建绘制上下文
DispatchQueue.global(qos: .userInitiated).async {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    defer { UIGraphicsEndImageContext() }
    
    // 绘制操作...
    let image = UIGraphicsGetImageFromCurrentImageContext()
    
    DispatchQueue.main.async {
        imageView.image = image
    }
}

七、 性能分析

  • 使用 Instruments 的 Core Animation 模板
  • 检查 drawRect: 的执行时间和频率
  • 监控 CPU 使用率和帧率

Swift 结构体属性:let 与 var 的选择艺术

作者 unravel2025
2025年8月12日 12:47

在 Swift 开发中,结构体(struct)的属性声明常面临 let 与 var 的抉择。本文将从多个维度解析两者的差异,并结合实际场景提供决策建议。

一、基础差异:不可变性与初始化行为

1. 不可变性的连锁反应

struct User {
    let id: UUID
    let imageURL: URL?
}

// 必须显式传递 nil
let user = User(id: UUID(), imageURL: nil)
  • 强制显式性let 属性要求初始化时必须赋值(包括 nil
  • 解码限制:若遵循 Decodablelet 属性会忽略 JSON 中的同名字段

2. 默认值的陷阱

struct User {
    let id = UUID() // 编译错误!无法覆盖默认值
}
  • 编译期锁定let 的默认值无法被外部赋值覆盖
  • 初始化器必要性:需手动实现初始化器才能保留默认值灵活性

二、进阶方案:平衡不可变性与便利性

1. 手动初始化器的优雅退场

struct User {
    let id: UUID
    let imageURL: URL?
    
    init(id: UUID = UUID(), imageURL: URL? = nil) {
        self.id = id
        self.imageURL = imageURL
    }
}
  • 双重优势:保持属性不可变的同时支持默认值
  • 维护成本:需手动编写和维护初始化逻辑

2. 属性包装器的魔法

@propertyWrapper struct Readonly<Value: Codable> {
    let wrappedValue: Value
}

struct User {
    @Readonly var id = UUID()
    @Readonly var imageURL: URL?
}
  • 复用性:通过包装器实现 var 声明的只读特性
  • 协议兼容:需额外实现 Encodable/Decodable 协议扩展

三、争议焦点:可变性的取舍

1. 极简主义路线

struct User {
    var id = UUID()
    var name: String
    // 其他属性均为 var
}
  • 测试友好:便于模拟状态变化(如 normalizeName() 测试)
  • 潜在风险:暴露不必要的可变性(需依赖调用者自律)

2. 结构体的本质思考

protocol UserTransformer {
    mutating func transform(_ user: inout User)
}

// 可能的滥用场景
struct UserIDTransformer: UserTransformer {
    func transform(_ user: inout User) {
        user = User(id: UUID(), name: user.name) // 完全替换实例
    }
}
  • 值类型的陷阱inout 参数允许完全替换底层实例
  • 防御性编程:重要属性应通过业务逻辑层保护

四、决策框架与最佳实践

1. 属性分类指南

属性类型 推荐修饰符 典型场景
核心标识符 let idprimaryKey
可选配置项 let? imageURL
计算衍生属性 var fullName
需要默认值 let+初始化器 createdAt = Date()

2. 实战建议

  1. **优先使用 let**:除非明确需要可变性
  2. 初始化器先行:通过自定义初始化保持 API 清晰
  3. 防御性包装:关键属性可通过访问控制限制修改权限
  4. **审慎使用 inout**:在需要改变实例时优先返回新实例

五、未来趋势展望

随着 Swift 演进,以下方向值得关注:

  • 不可变集合:Swift 5.7+ 引入的 @resultBuilder 可能催生新型不可变模式
  • 值类型增强:SE-0353 提案探索更高效的值类型复制机制
  • 协程集成:Async/Await 与结构体的结合可能改变状态管理范式

结语let 与 var 的选择本质上是数据模型设计的哲学问题。建议采用「最小权限原则」——仅在必要时引入可变性,并通过清晰的接口契约约束变更行为。记住,Swift 的强大之处在于其表达能力,合理利用语言特性能让代码既安全又优雅。

使用 Swift 的 defer 管理状态清理(译文)

作者 unravel2025
2025年8月12日 12:09

在异步函数中处理清理逻辑时,defer语句能确保代码在当前作用域退出前执行,无论函数是正常结束、抛出错误还是被提前返回。本文将通过一个常见场景——显示/隐藏加载指示器——演示 defer的用法。

典型用例

func fetch() async {
    isLoading = true
    defer { isLoading = false } // 清理逻辑集中在此
    
    do {
        articles = try await service.fetchArticles()
    } catch {
        self.error = error.localizedDescription
    }
}

传统实现方式对比

  1. 多位置设置(易遗漏):
isLoading = true
do {
    ...
    isLoading = false
} catch {
    ...
    isLoading = false
}
  1. 单次底部设置(无法应对提前退出):
isLoading = true
do {
    ...
} catch {
    ...
}
isLoading = false // 若中途 return 或 await 取消,此行不会执行

defer 的优势

  • 统一清理入口:将清理逻辑集中在一处,避免分散在各分支。

  • 容错性:自动处理以下场景:

    • 函数正常结束
    • 抛出错误
    • 提前返回(return
    • await过程中被取消

适用场景扩展

  • 资源释放:文件句柄、网络连接等
  • UI 状态管理:进度条、禁用按钮等
  • 并发安全:配合 actor防止竞态条件

总结

defer是 Swift 中管理临时状态和资源的利器,尤其适合异步编程场景。通过将清理逻辑延迟到作用域结束时执行,它能显著提升代码的健壮性和可维护性,降低因流程分支复杂导致的遗漏风险。对于需要确保执行清理操作的场景(如加载状态、锁机制等),建议优先考虑使用 defer

成年人的沟通,不谈钱谈什么?谈感情?

作者 iOS研究院
2025年8月12日 11:56

背景

正所谓,男人不好色好什么?How are you 么?

表情包.png

本来今天准备了另外的一个素材,准备发稿了。突然有个xxv加我,看着头像就知道不一般,结果果然不一般。

对话截图

对话截图.png

为啥三个不?

首先,我要表明自身立场确实在不方便接语音。其次,遇到的白嫖党太多了习惯了。所以,我习惯丑话说前边。

本质上成年人的社交,本来就应该是高效的。如果愿意付费就聊聊,不愿意付费就算了,没必要浪费彼此的时间

毕竟大家的时间都挺值钱,没必要互相耽误

对于这样就别做生意了那就更可笑了,你在教我做事?

我用了最简单的三个不,就是为了过滤这种低价低质量的交流。

同时,再次声明一下。我从16年就开始从事iOS开发的工作,从20年iOS审核过审严格之后截至目前,已经累计折损了60个账号。这里边有部分是公司,有部分是个人试错。

公众号咨询只不过是把之前账号成本回回血,顺便真正帮助一些想上架却上不去的同行。

有独立开发者身份傍身,即使不做咨询也依旧可以很潇洒。因为自身暂时没有什么好的产品,而且工具类产品暂时也不需要维护。

所以找了个公司混混社保,让自己时刻保持着学习的状态,在结交一些新的志同道合的朋友,拓展一下自己的人脉圈子。

写在最后

大家都是成年人别总想着像小孩子一样占便宜了。成人的免费往往都是最贵的,比如免费领鸡蛋,高价卖保jian品。再比如免费陪聊,引入杀🐷盘。

最近还看到一个见闻,易水寒一套时装10w+,而大多数游戏还停留在6元首冲的套路。

所以,与其做低价低质量的用户,不如分流提高客单价。以少胜多,以质取胜。

遵守规则,方得长治久安,最后祝大家大吉大利,今晚过审!

相关推荐

# Pingpong和连连的平替,让AppStore收款无需新增持有人。

# 苹果加急审核是“绿色通道”还是“死亡陷阱”?

# 苹果开发者邮箱,突然收到11.2通知严重么?

# 不想被苹果卡审最好错开这两个提审时间

# 手撕苹果审核4.3是代码问题还是设计问题?

# 有幸和Appstore审核人员进行了一场视频会议特此记录。

知识星球

更多Appstore咨询问题,请关注知识星球。「提供1v1上架指导,帮助开发者解决Appstore的疑难杂症,助力每一位开发者!」

把 GPT 塞进 iPhone:iOS 26 的 Foundation Models 框架全解析

作者 unravel2025
2025年8月12日 11:23

十分钟学会调用苹果原生大模型,不联网、不走流量、不丢隐私

一、为什么开发者要开始关心「苹果自己的大模型」?

WWDC 24 的 Platforms State of the Union 上,苹果第一次把「Apple Intelligence」背后的技术栈公开:Foundation Models 框架。

一句话总结:它让 3B 规模的大语言模型 直接在 A17/M3 系列芯片上跑——CPU + GPU + Neural Engine 混合计算,速度≈本地 Core ML;不联网、不上云、不泄露隐私。

本文基于 iOS 26 Beta API 写成,正式版可能微调,但思路 100 % 可用。

二、一分钟速览:Foundation Models 框架核心概念

概念 作用 对应代码
SystemLanguageModel 获取系统内置模型(3B 通用、内容打标签、摘要等适配器) SystemLanguageModel.default
LanguageModelSession 对话会话,保存上下文 LanguageModelSession(...)
@Generable 把 JSON 结构映射成 Swift 原生类型 @Generable struct Weather {...}
@Guide 对字段加约束(枚举、正则、描述) @Guide(.anyOf(["PG-13","R"]))
Tool 协议 让模型调用你的函数 FindRestaurantsTool()

三、实战:用 20 行代码做一个离线聊天机器人

  1. 检查模型可用性
import FoundationModels

let model = SystemLanguageModel.default
switch model.availability {
    case .available:
        break // OK
    case .unavailable(let reason):
        print("❌ \(reason)") // 设备不支持 or 正在下载
}

实测:A17 Pro + iOS 26 Beta 冷启动下载 1.8 GB 权重,首次后秒开。

  1. 创建会话
let session = LanguageModelSession {
    """
    你是 WWDC 小助手,回答简洁,用中文。
    """
}
  1. 发送/接收
let reply = try await session.respond(to: "Swift 的 async let 怎么用?")
print(reply.content) // 带代码示例的中文解释
  1. 流式输出(打字机效果)
let stream = try await session.streamResponse(to: "写一首五言绝句")
for try await partial in stream {
    updateUI(partial) // SwiftUI Text 逐字刷新
}
  1. 结构化输出(JSON → Swift 类型)
@Generable struct Weather {
    let temperature: Double
    let condition: String
}

let res = try await session.respond(
    to: "给我今日北京的天气",
    generating: Weather.self
)
print("气温 \(res.content.temperature)°C")

四、进阶:让模型调用你的 App 函数

场景:用户说「找三里屯附近的日料」→ 模型自动调用定位 + 网络搜索。

  1. 定义工具
final class FindRestaurantsTool: Tool {
    struct Args: Generable {
        let query: String
        @Guide(.count(3)) let maxResults: Int
    }
    
    func call(arguments: Args) async throws -> ToolOutput {
        let list = await LocationService.search(query: arguments.query,
                                                limit: arguments.maxResults)
        return ToolOutput(list.joined(separator: ", "))
    }
}
  1. 注入会话
let session = LanguageModelSession(
    tools: [FindRestaurantsTool()]
)
  1. 用户一句话触发
let result = try await session.respond(to: "三里屯附近好吃的日料")
print(result.content) // "寿司郎, 筑底海鲜市场, 将太无二"

五、性能 & 隐私 FAQ

六、开发 checklist(上线前必看)

• ✅ 在 Info.plist 增加 NSAppleIntelligenceUsageDescription

• ✅ 处理 model.availability == .downloading 的 UI 状态

• ✅ 使用 session.prewarm() 提前预热模型

• ✅ 对 VoiceOver 加 .accessibilityLabel("AI 回复")

• ✅ 高并发场景复用同一 LanguageModelSession,避免重复加载权重

七、一句话总结

Foundation Models 把「大模型」变成了和 UserDefaults 一样的基础设施:

声明式调用、类型安全、零网络、零隐私顾虑。

如果你今天开始做 iOS 16/17 适配,不妨把「本地大模型能力」写进 PRD —— 明年用户换机后就能直接体验。

八、延伸阅读 & 官方链接

• Foundation Models | Apple Developer Documentation developer.apple.com/documentati…

• Platforms State of the Union (WWDC 24) developer.apple.com/wwdc24/102

• Sample Code(官方 Demo):CreateWithSwift/FoundationModels-Demo

用 SwiftUI 打造“会长大”的组件 —— 从一次性 Alert 到可扩展设计系统

作者 unravel2025
2025年8月12日 10:43

原文链接

为什么旧写法撑不过三次迭代?

先来看一个“经典”写法

Alert(
    title: "Title",
    message: "Description",
    type: .info,
    showBorder: true,
    isDisabled: false,
    primaryButtonTitle: "OK",
    secondaryButtonTitle: "Cancel",
    primaryAction: { /* ... */ },
    secondaryAction: { /* ... */ }
)

痛点一句话总结:初始化即地狱。

• 参数爆炸,阅读困难

• 布局/样式/行为耦合,一改全改

• 无法注入自定义内容,复用性 ≈ 0

目标:像原生一样的 SwiftUI 组件

我们想要的最终形态:

AlertView(title: "...", message: "...") {
    AnyViewBuilder Content
}
.showBorder(true)
.disabled(isLoading)

为此,需要遵循 4 个关键词:

  1. Familiar APIs – 看起来像 SwiftUI 自带的
  2. Composability – 任意组合内容
  3. Scalability – 业务扩张不炸窝
  4. Accessibility – 无障碍不打补丁

三步重构法

Step 1:只保留「必须参数」

public struct AlertView: View {
    private let title: String
    private let message: String
    
    public init(title: String, message: String) {
        self.title = title
        self.message = message
    }
    
    public var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text(title).font(.headline)
            Text(message).font(.subheadline)
        }
        .padding()
    }
}

经验:先把最常用、不可省略的参数放进 init,其余全部踢出去。这一步就能干掉 70% 的参数。

Step 2:用 @ViewBuilder 把“内容”交出去

public struct AlertView<Footer: View>: View {
    private let title: String
    private let message: String
    private let footer: Footer
    
    public init(
        title: String,
        message: String,
        @ViewBuilder footer: () -> Footer
    ) {
        self.title = title
        self.message = message
        self.footer = footer()
    }
    
    public var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text(title).font(.headline)
            Text(message).font(.subheadline)
            footer.padding(.top, 25)
        }
        .padding()
    }
}

使用:

AlertView(title: "提示", message: "确定删除吗?") {
    HStack {
        Button("取消", role: .cancel) {}
        Button("删除", role: .destructive) {}
    }
}

Step 3:样式/行为用 环境值 + 自定义修饰符

我们想让边框可开关,但又不想回到“参数爆炸”。

struct ShowBorderKey: EnvironmentKey {
    static let defaultValue = false
}

extension EnvironmentValues {
    var showBorder: Bool {
        get { self[ShowBorderKey.self] }
        set { self[ShowBorderKey.self] = newValue }
    }
}

extension View {
    public func showBorder(_ value: Bool) -> some View {
        environment(\.showBorder, value)
    }
}

在 AlertView 内部读取

@Environment(\.showBorder) private var showBorder

// …
.overlay(
    RoundedRectangle(cornerRadius: 12)
        .stroke(Color.accentColor, lineWidth: showBorder ? 1 : 0)
)

至此,API 回归简洁:

AlertView(...) { ... }
    .showBorder(true)

进阶:用 @resultBuilder 做「有约束的自由」

当设计规范新增“免责声明 + 倒计时”组合时,与其疯狂加 init,不如定义一个 InfoSectionBuilder:

@resultBuilder
public struct InfoSectionBuilder {
    public static func buildBlock(_ disclaimer: Text) -> some View {
        disclaimer.disclaimerStyle()
    }
    public static func buildBlock(_ timer: TimerView) -> some View {
        timer
    }
    public static func buildBlock(
        _ disclaimer: Text,
        _ timer: TimerView
    ) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            disclaimer.disclaimerStyle()
            timer
        }
    }
}

把 AlertView 再升级一次:

public struct AlertView<Info: View, Footer: View>: View {
    private let title, message: String
    private let infoSection: Info
    private let footer: Footer
    
    public init(
        title: String,
        message: String,
        @InfoSectionBuilder infoSection: () -> Info,
        @ViewBuilder footer: () -> Footer
    ) {
        self.title = title
        self.message = message
        self.infoSection = infoSection()
        self.footer = footer()
    }
    
    public var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text(title).font(.headline)
            Text(message).font(.subheadline)
            infoSection.padding(.top, 16)
            footer.padding(.top, 25)
        }
        .padding()
    }
}

用法:

AlertView(
    title: "删除账户",
    message: "此操作不可撤销",
    infoSection: {
        Text("余额将在 24 小时内退回")
        TimerView(targetDate: .now + 100)
    },
    footer: {
        Button("确认删除", role: .destructive) {}
    }
)

无障碍:组件方 + 使用方 共同责任

组件内部负责结构级:

.accessibilityElement(children: .combine)
.accessibilityLabel("\(type.rawValue) alert: \(title). \(message)")
.accessibilityAddTraits(.isModal)

使用方负责内容级:

Button("延长会话") {}
    .accessibilityHint("延长 30 分钟")
    .accessibilityAction(named: "延长会话") { // 实际逻辑 }

写在最后的 checklist

维度 ✅ 自检问题
初始化 是否只有“最少必要参数”?
可组合 是否使用 @ViewBuilder / @resultBuilder
样式扩展 是否通过 EnvironmentKey + 自定义修饰符?
无障碍 结构 + 内容 是否都提供了 label / hint / action?
向后兼容 新增需求是否只“加 Builder 方法”而不是“改 init”?

源码仓库

所有示例已整理到 GitHub(非官方镜像,可直接跑 playground): github.com/muhammadosa…

当你用 .disabled(true) 把一整块区域关掉,子组件自动变灰、按钮自动失效 —— 这种「像原生」的体验,正是可扩展设计系统给人的最大安全感。

Swift Concurrency从入门到精通

2025年8月12日 08:58

如果在GPT上问如何精通swift并发你得到的答案会是这样

GPT回答如何精通swift并发

精通 Swift Concurrency(Swift 并发编程)需要你从概念 → API → 实战 → 性能优化四个阶段系统学习,并且在不同类型的项目中反复练习。下面我帮你分成一个循序渐进的路线图,并附上关键知识点与练习建议。


阶段 1:打牢基础(理解并发模型)

Swift Concurrency 在 Swift 5.5(iOS 15/macOS 12)引入,基于 async/awaitTaskActor 以及结构化并发(Structured Concurrency)

你要掌握的核心概念:

  1. 同步 vs 异步:理解阻塞与非阻塞

  2. 并发 vs 并行:并发是任务交错执行,并行是多核同时执行

  3. 线程安全数据竞争

  4. Swift Concurrency 的三大基石:

    • async/await
    • Task / TaskGroup
    • Actor

建议练习:

  • 写一个使用 async 函数下载多张图片并显示的简单 Demo
  • Task.sleep 模拟异步延迟
  • 使用 TaskGroup 并行处理多个网络请求

阶段 2:熟练掌握 Swift Concurrency API

必学 API 与关键点:

  1. async / await

    • 如何将回调式 API 改写成 async 函数(使用 withCheckedContinuation
    • 处理错误:try await
  2. Task 与取消任务

    • Task {} 创建任务
    • Task.detached 与作用域
    • Task.isCancelled 检查取消
  3. TaskGroup(并发任务组)

    • withTaskGroup
    • 并行批量任务 + 收集结果
  4. Actor(状态隔离)

    • 避免数据竞争
    • nonisolated 修饰符
  5. MainActor

    • 保证 UI 更新在主线程执行
  6. AsyncSequence 与异步流

    • for await 循环
    • AsyncStream 创建异步数据流

阶段 3:结合真实项目场景

  1. 网络层改造

    • async/await 改写 URLSession 请求
    • 结合 TaskGroup 批量请求数据
  2. UI 交互优化

    • 异步加载大图并在 MainActor 更新
  3. 后台任务

    • Task.detached 处理非 UI 逻辑
  4. 数据同步

    • Actor 封装共享数据状态
  5. 流式数据

    • 使用 AsyncStream 实现 WebSocket 消息流

阶段 4:进阶与性能优化

  1. 取消与资源管理

    • 学会在耗时任务中正确响应取消请求
  2. 并发限制

    • 使用 Semaphore 或自定义 TaskGroup 限制并发数
  3. 调试

    • 使用 Instruments 的 Concurrency Template 分析任务执行情况
  4. 与 GCD/Operation 混用

    • 明确什么时候保留 GCD(如与旧库集成)

async/await

这俩关键字的作用是用写同步代码的方式来写异步逻辑,避免传统基于多线程+回调的异步编程模式所具有的回调地狱的问题

Task

可异步执行的代码块,支持控制并发任务的生命周期、结构化并发与取消传播

actor

用编译器检查的方式保证可变状态在并发中安全访问(数据隔离模型),避免手工加锁

学习资源推荐

Apple WWDC 视频

  • Meet async/await in Swift
  • Explore structured concurrency in Swift
  • Protect mutable state with Swift actors

书籍

  • Using Async/Await in Swift by Marin Todorov
  • Swift Concurrency by Tutorials(raywenderlich)

官方文档

Swift Concurrency

Adopting strict concurrency in Swift 6 Apps

Running Queries with Swift Concurrency

总结起来就是异步的查询用HKAsyncQuery,异步的长时查询用HKAsyncSequenceQuery

Swift Concurrency 版 HealthKit 查询方式介绍
  • HealthKit 提供了一个 纯 Swift 异步查询接口,用于运行符合条件的查询并获取当前存储的所有匹配结果。
  • 支持 Swift 的 async/await,使异步查询流程更清晰、易读、无回调地狱。
    • Query Descriptor 是查询的核心构建类型,HealthKit 提供多种 *QueryDescriptor 类型,用于描述不同类型的数据查询:
Descriptor 类型 功能描述
HKSampleQueryDescriptor 根据条件读取样本数据的快照(一次性返回当前所有匹配结果)。
HKActivitySummaryQueryDescriptor 读取运动活动摘要数据,创建一个快照。
HKAnchoredObjectQueryDescriptor 用于“锚点”查询,支持分批读取数据、捕捉后续数据更改,可用于增量同步。
HKQuantitySeriesSampleQueryDescriptor 用于读取与数值样本相关联的“系列数据”(如连续测量值)。

除以上常见类型外,还支持更多 HKAsyncSequenceQuery 类型,通过 AsyncSequence 异步流机制返回结果


查询执行方式
  • 每种 Query Descriptor 都符合 HKAsyncQuery 协议,代表一种异步“一次性查询”(one-shot query)。可以通过调用其 result(for:) 方法来获取查询结果
  • 如果需要排序或过滤结果,可以在生成 query descriptor 时提供 sortDescriptorpredicate

总结一览
功能模块 描述
Swift 异步查询 API 支持纯 async/await 查询 HealthKit 数据,取代回调接口
Query Descriptor 类型 提供多种类型描述器,涵盖样本、活动摘要、增量同步、数值系列等场景
执行方式 使用统一协议 HKAsyncQuery,调用 result(for:) 返回异步结果
高级特性 支持排序、过滤条件,部分查询支持持续监听和分批处理
❌
❌