Interface Builderで配置したビューのクラスを後から変更する方法


Interface Builderで一度配置したビューを別のクラスに置き換えたいことがたまにあります。自作のサブクラスに置き換えるのであれば、クラスの指定を変更すれば済みますが、UIViewUIImageViewに置き換えたいときなど、Interface Builderでやるには再度配置するしかなく、特にAuto Layoutの設定をしてしまった後だと非常に面倒な作業になります。

これを避けたければ中身のXMLを直接編集するしかありません(個人的にこれをxibの開腹手術と呼んでいます)。

UIViewをUIImageViewに置き換える

まず簡単な例としてUIViewUIImageViewに置き換えることを考えます。

単純にクラスの指定をUIImageViewに変更するのも手ではあります。しかしこれではIB上での扱いはただのUIViewと変わらず、imageの設定などができません。

ここではXMLを編集することでクラスを置き換えてみます。と言ってもXMLの書式を把握しているわけではないので、まずは置き換えたいクラスUIImageViewを配置してみます。

そしてxib(またはStoryboard)ファイルをXMLとして開きます。そのためには左カラムのProject Navigatorから目的のファイルを右クリックしてOpen As > Source Codeを選択します。

表示されたXMLから、置き換え元のクラスと置き換え先のクラスがどこにあるか探してください。要素が多くて探しにくい場合はviewについたID(右カラムのIdentity inspectorにObject IDとして表示されます)で検索すると楽です。

ここで重要なのは「id」と「frame」と「constraints」です。

これらの要素をUIImageView側にコピーして、UIViewの方は削除します。

再びProject Navigatorからファイルを右クリックし、Open As > Interface Builder XIB Documentを選択して、IBで開きます。

問題がなければ下図のように期待通りの結果が得られます。XMLの編集を間違えると最悪ファイルを開くたびにXcodeがクラッシュするので注意してください。

UITableViewCellをUICollectionViewCellに置き換える

より複雑な例としてUITableViewCellUICollectionViewCellに置き換えることを考えます。セルの上に配置した要素はそのままに、根元だけクラスを変更します。複雑なビュー階層とAuto Layoutを設定していた場合、これを最初から作り直すことは避けたいでしょう。

ここではわかりやすくするため、以下のようにcontentView直下にビューがひとつ置かれている単純な構造を考えます。

TableViewCellのXMLは以下のようになっています。

TableViewCell.xib
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="zdF-YS-G3Z">
            <rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
            <autoresizingMask key="autoresizingMask"/>
            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zdF-YS-G3Z" id="3eA-Vz-neI">
                <autoresizingMask key="autoresizingMask"/>
                <subviews>
                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6HF-V6-sxv" userLabel="Image View">
                        <rect key="frame" x="8" y="8" width="72" height="72"/>
                        <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                        <constraints>
                            <constraint firstAttribute="height" constant="72" id="8lk-Uc-lxx"/>
                            <constraint firstAttribute="width" constant="72" id="G96-X2-EQ8"/>
                        </constraints>
                    </view>
                </subviews>
                <constraints>
                    <constraint firstItem="6HF-V6-sxv" firstAttribute="top" secondItem="3eA-Vz-neI" secondAttribute="top" constant="8" id="PlM-tb-ObS"/>
                    <constraint firstItem="6HF-V6-sxv" firstAttribute="leading" secondItem="3eA-Vz-neI" secondAttribute="leading" constant="8" id="XlX-fl-gER"/>
                </constraints>
            </tableViewCellContentView>
            <point key="canvasLocation" x="271" y="133"/>
        </tableViewCell>
    </objects>
</document>

一方、CollectionViewCellは以下のようになっています。

CollectionViewCell.xib
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="KEu-Vi-azp">
            <rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
                <rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
                <autoresizingMask key="autoresizingMask"/>
                <subviews>
                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zjc-6s-snt" userLabel="Image View">
                        <rect key="frame" x="8" y="8" width="72" height="72"/>
                        <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                        <constraints>
                            <constraint firstAttribute="width" constant="72" id="VjM-p5-jxX"/>
                            <constraint firstAttribute="height" constant="72" id="qmW-i8-JdR"/>
                        </constraints>
                    </view>
                </subviews>
                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
            </view>
            <constraints>
                <constraint firstItem="Zjc-6s-snt" firstAttribute="leading" secondItem="KEu-Vi-azp" secondAttribute="leading" constant="8" id="3JU-Lw-Ko2"/>
                <constraint firstItem="Zjc-6s-snt" firstAttribute="top" secondItem="KEu-Vi-azp" secondAttribute="top" constant="8" id="nW8-az-cXE"/>
            </constraints>
            <point key="canvasLocation" x="253" y="188"/>
        </collectionViewCell>
    </objects>
</document>

上記ではわかりにくいので、重要な部分だけ抜き出してみると以下のようになります。TableViewCellとContentViewCellでは構造が少し違っているのがわかります。

これを参考に、以下の手順でTableViewCellからCollectionViewCellへの移植が可能です。

  1. collectionViewCellのIDをtebleViewCellContentViewのIDに合わせる
  2. tebleViewCellContentViewの下のsubviewsをcollection viewの<view key="contentView">の下にコピー
  3. tebleViewCellContentViewの下のconstraintscollectionViewCellの下にコピー

collection viewではsubviewとconstraintの置かれている階層が違うことに注意が必要です。