mcpField
S
swift-uikit

swift-uikit

github.com/pluginagentmarketplace開発861 回インストール43 週間前に更新
リポジトリ

swift-uikit について

従来型の iOS インターフェースを開発する人向けに、UIKit の知識を提供するスキルです。ビューコントローラー、Auto Layout、テーブル・コレクションビュー、アクセシビリティ、一般的な問題の解決方法を扱います。

主な機能

  1. 01ビューコントローラーのパターンとナビゲーション
  2. 02Auto Layout の制約
  3. 03テーブル・コレクションビューのレイアウト
  4. 04アクセシビリティと Dynamic Type の確認
  5. 05UIKit の一般的な問題の解決方法

活用シーン

  1. 01コードや storyboard による UIKit 画面の構築
  2. 02リストやグリッド画面の作成
  3. 03制約の競合やセル未表示のデバッグ
  4. 04アクセシビリティと Dynamic Type の対応確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pluginagentmarketplace/custom-plugin-swift --skill swift-uikit

インストール後、Claude Code で/swift-uikitを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
config.yaml
schema.json
ViewController.swift.template
references/
GUIDE.md
PATTERNS.md
scripts/
validate.py

詳細

UIKit スキル

従来の iOS ユーザーインターフェイスを構築するための、UIKit フレームワークに関する包括的な知識です。

前提条件

  • Xcode 15 以降がインストールされていること
  • iOS 15 以降のデプロイメントターゲットを推奨
  • MVC/MVVM パターンの理解

パラメーター

parameters:
  layout_approach:
    type: string
    enum: [programmatic, storyboard, xib]
    default: programmatic
  accessibility_enabled:
    type: boolean
    default: true
  dynamic_type_support:
    type: boolean
    default: true

対象トピック

ビューコントローラー
種類目的使用例
UIViewController基本コントローラーカスタム画面
UINavigationControllerスタックナビゲーション階層型のフロー
UITabBarControllerタブベースメインセクション
UISplitViewControllerマスター/詳細iPad のレイアウト
UIPageViewControllerページスワイプオンボーディング
Auto Layout
制約の種類説明
Leading/Trailing水平方向の端(RTL に対応)
Top/Bottom垂直方向の端
CenterX/CenterY中央揃え
Width/Heightサイズ制約
アスペクト比比例サイズ
テーブルビューとコレクションビュー
コンポーネント目的
UITableView垂直スクロールリスト
UICollectionView柔軟なグリッドレイアウト
DiffableDataSource自動差分計算(iOS 13 以降)
CompositionalLayout複雑なレイアウト(iOS 13 以降)

コード例

プログラムによるビューコントローラー
final class ProfileViewController: UIViewController {
    // MARK: - UI Components

    private lazy var avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 50
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.accessibilityLabel = "Profile picture"
        return imageView
    }()

    private lazy var nameLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .title1)
        label.adjustsFontForContentSizeCategory = true
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private lazy var bioLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .body)
        label.adjustsFontForContentSizeCategory = true
        label.numberOfLines = 0
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupConstraints()
    }

    // MARK: - Setup

    private func setupUI() {
        view.backgroundColor = .systemBackground
        view.addSubview(avatarImageView)
        view.addSubview(nameLabel)
        view.addSubview(bioLabel)
    }

    private func setupConstraints() {
        NSLayoutConstraint.activate([
            avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 32),
            avatarImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 100),
            avatarImageView.heightAnchor.constraint(equalToConstant: 100),

            nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 16),
            nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),

            bioLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            bioLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            bioLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }

    // MARK: - Configuration

    func configure(with user: User) {
        nameLabel.text = user.name
        bioLabel.text = user.bio
        // Load avatar image
    }
}
Diffable Data Source を使用した最新のコレクションビュー
final class ProductGridViewController: UIViewController {
    enum Section { case main }

    private var collectionView: UICollectionView!
    private var dataSource: UICollectionViewDiffableDataSource<Section, Product>!

    override func viewDidLoad() {
        super.viewDidLoad()
        configureCollectionView()
        configureDataSource()
    }

    private func configureCollectionView() {
        let layout = createCompositionalLayout()
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.backgroundColor = .systemBackground
        view.addSubview(collectionView)
    }

    private func createCompositionalLayout() -> UICollectionViewLayout {
        let itemSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.5),
            heightDimension: .estimated(200)
        )
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)

        let groupSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .estimated(200)
        )
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section = NSCollectionLayoutSection(group: group)
        return UICollectionViewCompositionalLayout(section: section)
    }

    private func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<ProductCell, Product> { cell, indexPath, product in
            cell.configure(with: product)
        }

        dataSource = UICollectionViewDiffableDataSource<Section, Product>(collectionView: collectionView) {
            collectionView, indexPath, product in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: product)
        }
    }

    func applySnapshot(products: [Product], animatingDifferences: Bool = true) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Product>()
        snapshot.appendSections([.main])
        snapshot.appendItems(products)
        dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
    }
}
固有サイズを持つカスタム UIView
final class TagView: UIView {
    private let label: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .caption1)
        label.adjustsFontForContentSizeCategory = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private let padding = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)

    override var intrinsicContentSize: CGSize {
        let labelSize = label.intrinsicContentSize
        return CGSize(
            width: labelSize.width + padding.left + padding.right,
            height: labelSize.height + padding.top + padding.bottom
        )
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        backgroundColor = .systemBlue
        layer.cornerRadius = 8

        addSubview(label)
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: topAnchor, constant: padding.top),
            label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
            label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right),
            label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom)
        ])
    }

    func configure(text: String, color: UIColor = .systemBlue) {
        label.text = text
        label.textColor = .white
        backgroundColor = color
        invalidateIntrinsicContentSize()
    }
}

トラブルシューティング

よくある問題
問題原因解決策
"Unable to simultaneously satisfy constraints"制約の競合柔軟な制約の優先度を下げる
セルが表示されない登録漏れCellRegistration を使用するか、デキューする前に登録する
キーボードがテキストフィールドに重なるキーボード処理がないキーボード通知を監視する
コンテンツが圧縮されるハギング優先度が設定されていないコンテンツのハギング優先度を高く設定する
ビューがタッチに反応しないビューが重なっているビュー階層と userInteractionEnabled を確認する
デバッグコマンド
// Print view hierarchy
po view.recursiveDescription()

// Print Auto Layout issues
po view.constraintsAffectingLayout(for: .horizontal)

// Check if on main thread
assert(Thread.isMainThread, "UI updates must be on main thread")

検証ルール

validation:
  - rule: accessibility_labels
    severity: warning
    check: All interactive elements must have accessibility labels
  - rule: dynamic_type_support
    severity: warning
    check: Use preferredFont and adjustsFontForContentSizeCategory
  - rule: safe_area_constraints
    severity: info
    check: Content should respect safe area layout guides

使用方法

Skill("swift-uikit")

関連スキル

  • swift-ios-basics - iOS の基礎
  • swift-swiftui - 最新の代替手段
  • swift-architecture - アプリのアーキテクチャ

コメント