Token导航 LogoToken导航

Agent Skill · 研究检索

swiftui-uikit-interop(Swiftui uikit 互操作)

swiftui-uikit-interop 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

swiftui-uikit-interop 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证信息还不明确,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 4,842。

总安装 29,376周安装 1,187GitHub Stars 461下载 9,408核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:swiftui-uikit-interop(Swiftui uikit 互操作)
来源仓库:https://github.com/dpearson2699/swift-ios-skills
仓库路径:skills/swiftui-uikit-interop
安装命令:
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-uikit-interop
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-uikit-interop

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

将 UIKit 视图和控制器包装在 SwiftUI 中,将 SwiftUI 嵌入 UIKit 中,并在两个框架之间同步状态。

使用 UIViewRepresentable 包装 UIKit 视图(文本视图、地图视图、Web 视图)和 UIViewControllerRepresentable 用于模态控制器(文档扫描仪、邮件撰写器、选择器);两者都依赖协调器模式来桥接委托回调 使用 UIHostingController 在 UIKit 中嵌入 SwiftUI 视图 使用适当的子容器(addChild ,添加视图,didMove ),或使用 UIHostingConfiguration 用于集合/表格单元格(iOS 16+) 通过@Binding同步状态 用于双向同步、单向事件的关闭以及主题/辅助功能更改的环境值;通过在重新分配 UIKit 属性之前进行检查来防止更新循环 在 makeUIView 中设置委托并创建视图一次 / makeUIViewController ,永远不会在 updateUIView 中 ;清理 dismantleUIView 中的观察者和计时器 防止泄漏和过时的回调

来源仓库内路径:skills/swiftui-uikit-interop

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

swiftui-uikit-interop 怎么安装?

在终端执行 npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-uikit-inte…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

swiftui-uikit-interop 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

swiftui-uikit-interop 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

swiftui-uikit-interop 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

swiftui-uikit-interop 用的人多吗?

公开数据是累计安装 29,376 次,近一周安装 1,187 次,下载 9,408 次,GitHub 461 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

swiftui-uikit-interop 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
swiftui-uikit-interop

Swiftui uikit 互操作 · 本页

29,376461只读未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SwiftUI-UIKit Interop

Bridge UIKit and SwiftUI in both directions. Wrap UIKit views and view controllers for use in SwiftUI, embed SwiftUI views inside UIKit screens, and synchronize state across the boundary. Targets iOS 26+ with Swift 6.3 patterns; notes backward-compatible to iOS 16 unless stated otherwise.

See references/representable-recipes.md for complete wrapping recipes and references/hosting-migration.md for UIKit-to-SwiftUI migration patterns.

Contents

UIViewRepresentable Protocol

Use UIViewRepresentable to wrap any UIView subclass for use in SwiftUI.

Required Methods

struct WrappedTextView: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        // Called ONCE when SwiftUI inserts this view into the hierarchy.
        // Create and return the UIKit view. One-time setup goes here.
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = .preferredFont(forTextStyle: .body)
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        // Called on EVERY SwiftUI state change that affects this view.
        // Synchronize SwiftUI state into the UIKit view.
        // Guard against redundant updates to avoid loops.
        if uiView.text != text {
            uiView.text = text
        }
    }
}

Lifecycle Timing

MethodWhen CalledPurpose
makeCoordinator()Before makeUIView. Once per representable lifetime.Create the delegate/datasource reference type.
makeUIView(context:)Once, when the representable enters the view tree.Allocate and configure the UIKit view.
updateUIView(_:context:)Immediately after makeUIView, then on every relevant state change.Push SwiftUI state into the UIKit view.
dismantleUIView(_:coordinator:)When the representable is removed from the view tree.Clean up observers, timers, subscriptions.
sizeThatFits(_:uiView:context:)During layout, when SwiftUI needs the view's ideal size. iOS 16+.Return a custom size proposal.

Why updateUIView is the most important method: SwiftUI calls it every time any @Binding, @State, @Environment, or @Observable property read by the representable changes. All state synchronization from SwiftUI to UIKit happens here. If you skip a property, the UIKit view will fall out of sync.

Optional: dismantleUIView

static func dismantleUIView(_ uiView: UITextView, coordinator: Coordinator) {
    // Remove observers, invalidate timers, cancel subscriptions.
    // The coordinator is passed in so you can access state stored on it.
    coordinator.cancellables.removeAll()
}

Optional: sizeThatFits (iOS 16+)

@available(iOS 16.0, *)
func sizeThatFits(
    _ proposal: ProposedViewSize,
    uiView: UITextView,
    context: Context
) -> CGSize? {
    // Return nil to fall back to UIKit's intrinsicContentSize.
    // Return a CGSize to override SwiftUI's sizing for this view.
    let width = proposal.width ?? UIView.layoutFittingExpandedSize.width
    let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
    return size
}

UIViewControllerRepresentable Protocol

Use UIViewControllerRepresentable to wrap a UIViewController subclass -- typically for system pickers, document scanners, mail compose, or any controller that presents modally.

struct DocumentScannerView: UIViewControllerRepresentable {
    @Binding var scannedImages: [UIImage]
    @Environment(\.dismiss) private var dismiss

    func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
        let scanner = VNDocumentCameraViewController()
        scanner.delegate = context.coordinator
        return scanner
    }

    func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {
        // Usually empty for modal controllers -- nothing to push from SwiftUI.
    }

    func makeCoordinator() -> Coordinator { Coordinator(self) }
}

Handling Results from Presented Controllers

The coordinator captures delegate callbacks and routes results back to SwiftUI through the parent's @Binding or closures:

extension DocumentScannerView {
    final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
        let parent: DocumentScannerView

        init(_ parent: DocumentScannerView) { self.parent = parent }

        func documentCameraViewController(
            _ controller: VNDocumentCameraViewController,
            didFinishWith scan: VNDocumentCameraScan
        ) {
            parent.scannedImages = (0..<scan.pageCount).map { scan.imageOfPage(at: $0) }
            parent.dismiss()
        }

        func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
            parent.dismiss()
        }

        func documentCameraViewController(
            _ controller: VNDocumentCameraViewController,
            didFailWithError error: Error
        ) {
            parent.dismiss()
        }
    }
}

The Coordinator Pattern

Why Coordinators Exist

UIKit delegates, data sources, and target-action patterns require a reference type (class). SwiftUI representable structs are value types and cannot serve as delegates. The Coordinator is a class instance that SwiftUI creates and manages for you -- it lives as long as the representable view.

Structure

Always nest the Coordinator inside the representable or in an extension. Store a reference to parent (the representable struct) so the coordinator can write back to @Binding properties.

struct SearchBarView: UIViewRepresentable {
    @Binding var text: String
    var onSearch: (String) -> Void

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    func makeUIView(context: Context) -> UISearchBar {
        let bar = UISearchBar()
        bar.delegate = context.coordinator  // Set delegate HERE, not in updateUIView
        return bar
    }

    func updateUIView(_ uiView: UISearchBar, context: Context) {
        if uiView.text != text {
            uiView.text = text
        }
    }

    final class Coordinator: NSObject, UISearchBarDelegate {
        var parent: SearchBarView

        init(_ parent: SearchBarView) { self.parent = parent }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            parent.text = searchText
        }

        func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
            parent.onSearch(parent.text)
            searchBar.resignFirstResponder()
        }
    }
}

Key Rules

  1. Set the delegate in makeUIView/makeUIViewController, never in updateUIView. The update method runs on every state change -- setting the delegate there causes redundant assignment and can trigger unexpected side effects.
  2. The coordinator's parent property is updated automatically. SwiftUI updates the coordinator's reference to the latest representable struct value before each call to updateUIView. This means the coordinator always sees current @Binding values through parent.
  3. Use [weak coordinator] in closures to avoid retain cycles between the coordinator and UIKit objects that capture it.

UIHostingController

Embed SwiftUI views inside UIKit view controllers using UIHostingController.

Basic Embedding

final class ProfileViewController: UIViewController {
    private let hostingController = UIHostingController(rootView: ProfileView())

    override func viewDidLoad() {
        super.viewDidLoad()

        // 1. Add as child
        addChild(hostingController)

        // 2. Add and constrain the view
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(hostingController.view)
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])

        // 3. Notify the child
        hostingController.didMove(toParent: self)
    }
}

The three-step sequence (addChild, add view, didMove) is mandatory. Skipping any step causes containment callbacks to misfire, which breaks appearance transitions and trait propagation.

Sizing Options (iOS 16+)

@available(iOS 16.0, *)
hostingController.sizingOptions = [.intrinsicContentSize]
OptionEffect
.intrinsicContentSizeThe hosting controller's view reports its SwiftUI content size as intrinsicContentSize. Use in Auto Layout when the hosted view should size itself.
.preferredContentSizeUpdates preferredContentSize to match SwiftUI content. Use when presenting as a popover or form sheet.

Updating the Root View

When data changes in UIKit, push new state into the hosted SwiftUI view:

func updateProfile(_ profile: Profile) {
    hostingController.rootView = ProfileView(profile: profile)
}

For observable models, pass an @Observable object and SwiftUI tracks changes automatically -- no need to reassign rootView.

UIHostingConfiguration (iOS 16+)

Render SwiftUI content directly inside UICollectionViewCell or UITableViewCell without managing a child hosting controller:

@available(iOS 16.0, *)
func collectionView(
    _ collectionView: UICollectionView,
    cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
    cell.contentConfiguration = UIHostingConfiguration {
        ItemRow(item: items[indexPath.item])
    }
    return cell
}

Sizing and Layout

intrinsicContentSize Bridging

UIKit views wrapped in UIViewRepresentable communicate their natural size to SwiftUI through intrinsicContentSize. SwiftUI respects this during layout unless overridden by frame() or fixedSize().

fixedSize() and frame() Interactions

SwiftUI ModifierEffect on Representable
No modifierSwiftUI uses intrinsicContentSize as ideal size; the view is flexible.
.fixedSize()Forces the representable to its ideal (intrinsic) size in both axes.
.fixedSize(horizontal: true, vertical: false)Fixes width to intrinsic; height remains flexible.
.frame(width:height:)Overrides the proposed size; UIKit view receives this size.

Auto Layout with UIHostingController

When embedding UIHostingController as a child, pin its view with constraints. Use .sizingOptions = [.intrinsicContentSize] so Auto Layout can query the SwiftUI content's natural size for self-sizing cells or variable-height sections.

State Synchronization Patterns

@Binding: Two-Way Sync (SwiftUI <-> UIKit)

Use @Binding when both sides read and write the same value. The coordinator writes to parent.bindingProperty in delegate callbacks; updateUIView reads the binding and pushes it into the UIKit view.

// SwiftUI -> UIKit: in updateUIView
if uiView.text != text { uiView.text = text }

// UIKit -> SwiftUI: in Coordinator delegate method
func textViewDidChange(_ textView: UITextView) {
    parent.text = textView.text
}

Closures: One-Way Events (UIKit -> SwiftUI)

For fire-and-forget events (button tapped, search submitted, scan completed), pass a closure instead of a binding:

struct WebViewWrapper: UIViewRepresentable {
    let url: URL
    var onNavigationFinished: ((URL) -> Void)?
}

Environment Values

Access SwiftUI environment values inside representable methods via context.environment:

func updateUIView(_ uiView: UITextView, context: Context) {
    let isEnabled = context.environment.isEnabled
    uiView.isEditable = isEnabled

    // Respond to color scheme changes
    let colorScheme = context.environment.colorScheme
    uiView.backgroundColor = colorScheme == .dark ? .systemGray6 : .white
}

Avoiding Update Loops

updateUIView is called whenever SwiftUI state changes -- including changes triggered by the coordinator writing to a @Binding. Guard against redundant updates to prevent infinite loops:

func updateUIView(_ uiView: UITextView, context: Context) {
    // GUARD: Only update if values actually differ
    if uiView.text != text {
        uiView.text = text
    }
}

Without the guard, setting uiView.text may trigger the delegate's textViewDidChange, which writes to parent.text, which triggers updateUIView again.

Sendable Considerations

UIKit delegate protocols are not Sendable. When the coordinator conforms to a UIKit delegate, it inherits main-actor isolation from UIKit. Mark coordinators @MainActor or use nonisolated only for methods that truly do not touch UIKit state. In Swift 6 strict concurrency:

@MainActor
final class Coordinator: NSObject, UISearchBarDelegate {
    var parent: SearchBarView
    init(_ parent: SearchBarView) { self.parent = parent }
    // Delegate methods are main-actor-isolated -- safe to access UIKit and @Binding.
}

If passing closures across isolation boundaries, ensure they are @Sendable or captured on the correct actor.

Common Mistakes

DO / DON'T

DON'T: Create the UIKit view in updateUIView. DO: Create the view once in makeUIView; only configure/update it in updateUIView. *Why:* updateUIView runs on every state change. Creating a new view each time destroys all UIKit state (selection, scroll position, first responder) and leaks memory.

DON'T: Set delegates in updateUIView. DO: Set delegates in makeUIView/makeUIViewController only. *Why:* Redundant delegate assignment on every update can reset internal delegate state in UIKit views like WKWebView or MKMapView.

DON'T: Hold strong references to the Coordinator from closures. DO: Use [weak coordinator] in closures. *Why:* UIKit objects often store closures (completion handlers, action blocks). A strong reference to the coordinator that holds a reference to the UIKit view creates a retain cycle.

DON'T: Forget to call parent.dismiss() or completion handlers. DO: Use the coordinator to track dismissal and invoke parent.dismiss() in all delegate exit paths. *Why:* Modal controllers presented by SwiftUI (via .sheet) need their dismiss binding toggled, or the sheet state becomes inconsistent.

DON'T: Ignore dismantleUIView for views that hold observers or timers. DO: Clean up NotificationCenter observers, Combine subscriptions, and Timer instances in dismantleUIView. *Why:* Without cleanup, observers and timers continue firing after the view is removed, causing crashes or stale state updates.

DON'T: Force UIHostingController's view to fill the parent without proper constraints. DO: Use Auto Layout constraints or sizingOptions for proper embedding. *Why:* Setting frame manually breaks adaptive layout, trait propagation, and safe area handling.

DON'T: Try to use @State in the Coordinator -- it is not a View. DO: Use regular stored properties on the Coordinator and communicate to SwiftUI via parent's @Binding properties. *Why:* @State only works inside View conformances. Using it on a class has no effect.

DON'T: Skip the addChild/didMove(toParent:) dance when embedding UIHostingController. DO: Always call addChild(_:), add the view to the hierarchy, then call didMove(toParent:). *Why:* Skipping containment causes viewWillAppear/viewDidAppear to never fire, breaks trait collection propagation, and causes visual glitches.

Review Checklist

  • View/controller created in make*, not update*
  • Coordinator set as delegate in make*, not update*
  • @Binding used for two-way state sync
  • updateUIView handles all SwiftUI state changes with redundancy guards
  • dismantleUIView cleans up observers/timers if needed
  • No retain cycles between coordinator and closures ([weak coordinator])
  • UIHostingController properly added as child (addChild + didMove(toParent:))
  • Sizing strategy chosen (intrinsicContentSize vs fixed frame vs sizeThatFits)
  • Environment values read in updateUIView via context.environment where needed
  • Coordinator marked @MainActor for strict concurrency
  • Modal controllers dismiss in all delegate exit paths (success, cancel, error)
  • UIHostingConfiguration used for collection/table view cells instead of manual hosting (iOS 16+)

References

平台分布

Codex

35.68%
按下载量换算3,357

Claude

32.15%
按下载量换算3,025

Cursor

19.69%
按下载量换算1,852

Gemini CLI

9.95%
按下载量换算936

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills