web-dev-qa-db-ja.com

Swift-XIBファイルを使用してカスタムviewForHeaderInSectionを作成する方法

以下のようにプログラムで簡単なカスタムviewForHeaderInSectionを作成できます。しかし、別のクラスと接続して、tableViewセルのようなプロパティに到達するなど、もっと複雑なことをしたいと思います。単純に、私は自分が何をしているかを見たいです。

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    if(section == 0) {

        let view = UIView() // The width will be the same as the cell, and the height should be set in tableView:heightForRowAtIndexPath:
        let label = UILabel()
        let button   = UIButton(type: UIButtonType.System)

        label.text="My Details"
        button.setTitle("Test Title", forState: .Normal)
        // button.addTarget(self, action: Selector("visibleRow:"), forControlEvents:.TouchUpInside)

        view.addSubview(label)
        view.addSubview(button)

        label.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false

        let views = ["label": label, "button": button, "view": view]

        let horizontallayoutContraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[label]-60-[button]-10-|", options: .AlignAllCenterY, metrics: nil, views: views)
        view.addConstraints(horizontallayoutContraints)

        let verticalLayoutContraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0)
        view.addConstraint(verticalLayoutContraint)

        return view
    }

    return nil
}


func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 50
}

Xibを使用してカスタムtableViewヘッダービューを作成する方法を説明できる人はいますか?古いObj-Cの話題に出会ったことがありますが、Swift言語は初めてです。誰かが詳細に説明すれば、それは素晴らしいことです。

1.issue:ボタン@IBActionがViewControllerに接続しません。 (固定)

ファイルの所有者、ViewController基本クラスで解決しました(左のアウトラインメニューをクリックしました。)

2.issue:ヘッダーの高さの問題(修正済み)

headerView.clipsToBounds = trueviewForHeaderInSection:メソッドに追加して解決しました。

制約警告の場合この回答で問題が解決しました

ViewControllerのこのメソッドでImageViewに同じ高さの制約を追加した場合、tableViewの行に流れます picture like look

 func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 120
}

ViewDidLoadで自動的にAdjustsScrollViewInsetsを使用する場合、この場合、画像はnavigationBarの下に流れます。 -一定-

self.automaticallyAdjustsScrollViewInsets = false

3.issue:ビューの下のボタン(固定)

@IBAction func didTapButton(sender: AnyObject) {
    print("tapped")

    if let upView = sender.superview {
        if let headerView = upView?.superview as? CustomHeader {
            print("in section \(headerView.sectionNumber)")
        }

    }
}
33
burakgunduz

NIBベースのヘッダーの一般的なプロセスは次のとおりです。

  1. 少なくともラベルのアウトレットを持つUITableViewHeaderFooterViewサブクラスを作成します。また、このヘッダーが対応するセクションにリバースエンジニアリングできる識別子を付けることもできます。同様に、ヘッダーを使用してView Controllerにイベントを通知できるプロトコルを指定することもできます(ボタンのタップなど)。したがって、Swift 3以降では:

    // if you want your header to be able to inform view controller of key events, create protocol
    
    protocol CustomHeaderDelegate: class {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int)
    }
    
    // define CustomHeader class with necessary `delegate`, `@IBOutlet` and `@IBAction`:
    
    class CustomHeader: UITableViewHeaderFooterView {
        static let reuseIdentifier = "CustomHeader"
    
        weak var delegate: CustomHeaderDelegate?
    
        @IBOutlet weak var customLabel: UILabel!
    
        var sectionNumber: Int!  // you don't have to do this, but it can be useful to have reference back to the section number so that when you tap on a button, you know which section you came from; obviously this is problematic if you insert/delete sections after the table is loaded; always reload in that case
    
        @IBAction func didTapButton(_ sender: AnyObject) {
            delegate?.customHeader(self, didTapButtonInSection: section)
        }
    
    }
    
  2. NIBを作成します。個人的には、NIBに基本クラスと同じ名前を付けて、プロジェクト内のファイルの管理を簡素化し、混乱を避けています。とにかく、重要な手順は次のとおりです。

    • ビューNIBを作成するか、空のNIBで開始した場合は、NIBにビューを追加します。

    • ビューの基本クラスをUITableViewHeaderFooterViewサブクラス(この例ではCustomHeader)に設定します。

    • IBにコントロールと制約を追加します。

    • Swiftコード内のアウトレットへの@IBOutlet参照を接続します。

    • ボタンを@IBActionに接続します;そして

    • NIBのルートビューでは、背景色を必ず「デフォルト」に設定してください。設定しないと、背景色の変更に関する迷惑な警告が表示されます。

  3. View ControllerのviewDidLoadで、NIBを登録します。 Swift 3以降:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
    }
    
  4. viewForHeaderInSectionで、前の手順で指定したのと同じ識別子を使用して再利用可能なビューをデキューします。これを行うと、アウトレットを使用できるようになり、プログラムで作成された制約などで何もする必要がなくなります。(ボタンが機能するためのプロトコルのために)必要なのはデリゲートを指定することだけです。たとえば、Swift 3の場合:

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomHeader") as! CustomHeader
    
        headerView.customLabel.text = content[section].name  // set this however is appropriate for your app's model
        headerView.sectionNumber = section
        headerView.delegate = self
    
        return headerView
    }
    
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44  // or whatever
    }
    
  5. 明らかに、ビュービューコントローラーをヘッダービューのボタンのdelegateとして指定する場合、そのプロトコルに準拠する必要があります。

    extension ViewController: CustomHeaderDelegate {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
            print("did tap button", section)
        }
    }
    

関係するすべてのステップをリストすると、これはすべて混乱しているように聞こえますが、一度または2回実行すれば、非常に簡単です。プログラムでヘッダービューを作成するよりも簡単だと思います。


matt's answer で、彼は抗議しています:

問題は、簡単に言えば、IDインスペクターで宣言するだけでは、nibのUIViewUITableViewHeaderFooterViewに魔法のように変えることができないということです。

これは単に正しくありません。上記のNIBベースのアプローチを使用する場合、このヘッダービューのルートビューに対してインスタンス化されるクラスは、UITableViewHeaderFooterViewではなく、UIViewサブクラスです。 NIBのルートビューの基本クラスに指定したクラスをインスタンス化します。

ただし、正しいのは、このクラスのプロパティの一部(特に contentView )がこのNIBベースのアプローチでは使用されていないことです。 textLabeldetailTextLabel のように、オプションのプロパティである必要があります(または、IBでUITableViewHeaderFooterViewの適切なサポートを追加する必要があります)。これはApple側の貧弱なデザインであることに同意しますが、それはずさんな、特異な詳細として私に印象を与えますが、テーブルビューのすべての問題を考えると小さな問題です。たとえば、これらすべての年月が経過しても、ストーリーボードでプロトタイプのヘッダー/フッタービューをまったく実行できず、これらのNIBおよびクラス登録の手法にまったく依存しなければならないことは驚くべきことです。

しかし、iOS 6以降アクティブに使用されているAPIメソッドである register(_:forHeaderFooterViewReuseIdentifier:) を使用できないと結論付けるのは正しくありません。赤ちゃんを風呂の水で捨てないでください。


Swift 2レンディションについては、この回答の 前のリビジョン を参照してください。

81
Rob

ロブの答えは、説得力があると思われ、時の試練に耐えてきましたが、間違っており、常にそうでした。圧倒的な群衆の「知恵」の受け入れと多数の賛成に対抗するのは困難ですが、真実を語る勇気を呼び起こそうと思います。

問題は、非常に簡単に言えば、IDインスペクターで宣言するだけでは、nibのUIViewをUITableViewHeaderFooterViewに魔法のように変えることができないということです。 UITableViewHeaderFooterViewには、正しい操作の鍵となる重要な機能があります。また、プレーンなUIViewは、どのような方法であってもcastに欠けています。

  • UITableViewHeaderFooterViewには contentView があり、カスタムサブビューはすべて、UITableViewHeaderFooterViewではなく、これに追加する必要があります。

    しかし、UIViewが不思議なことにUITableViewHeaderFooterViewとしてキャストすると、nibにこのcontentViewがありません。したがって、Robが「コントロールと制約をIBに追加する」と言うとき、サブビュー[UITableViewHeaderFooterViewに直接追加し、contentViewnotを追加する必要があります。したがって、ヘッダーは誤って構成されます。

  • 問題のもう1つの兆候は、UITableViewHeaderFooterViewに背景色を与えることが許可されていないことです。すると、コンソールに次のメッセージが表示されます。

    UITableViewHeaderFooterViewの背景色の設定は廃止されました。代わりに、希望する背景色でカスタムUIViewをbackgroundViewプロパティに設定してください。

    ただし、nibでは、help UITableViewHeaderFooterViewに背景色を設定することはできません。また、doコンソールでそのメッセージを取得できます。

それでは、質問に対する正しい答えは何ですか? 不可能の答えがあります。 Appleは、ここで大失敗しました。 UITableViewHeaderFooterViewのソースとしてnibを登録できるメソッドを提供していますが、Object LibraryにはUITableViewHeaderFooterViewはありませんがあります。したがって、このメソッドはselessです。ペン先でUITableViewHeaderFooterViewを正しく設計するのはimpossibleです。

これはXcodeの大きなバグです。私はこの問題に関するバグ報告を提出しました2013年、まだそこに座って、開いています。私は毎年バグを修正し、Appleは「問題がどのように、またはいつ解決されるか決定されていません」と押し続けています。彼らはバグを認めますが、それについては何もしません。

ただし、canで行うことは、nibで通常のUIViewを設計し、その後、コードで(viewForHeaderInSectionの実装で)nibから手動でビューをロードし、contentViewに挿入します。ヘッダービュー。

たとえば、nibでヘッダーを設計し、ヘッダーに変数labを接続するラベルがあるとします。次に、カスタムヘッダークラ​​スとカスタムビュークラスの両方が必要です。

class MyHeaderView : UITableViewHeaderFooterView {
    weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
    @IBOutlet weak var lab : UILabel!
}

Nibではなく、ヘッダービューのclassを登録します。

self.tableView.register(MyHeaderView.self,
    forHeaderFooterViewReuseIdentifier: self.headerID)

ビューxibファイルで、ビューをMyHeaderViewContentに宣言します— not MyHeaderView。

viewForHeaderInSectionでは、nibからビューを取り出して、ヘッダーのcontentViewに詰め込み、それへの参照を構成します。

override func tableView(_ tableView: UITableView, 
    viewForHeaderInSection section: Int) -> UIView? {
    let h = tableView.dequeueReusableHeaderFooterView(
        withIdentifier: self.headerID) as! MyHeaderView
    if h.content == nil {
        let v = UINib(nibName: "MyHeaderView", bundle: nil).instantiate
            (withOwner: nil, options: nil)[0] as! MyHeaderViewContent
        h.contentView.addSubview(v)
        v.translatesAutoresizingMaskIntoConstraints = false
        v.topAnchor.constraint(equalTo: h.contentView.topAnchor).isActive = true
        v.bottomAnchor.constraint(equalTo: h.contentView.bottomAnchor).isActive = true
        v.leadingAnchor.constraint(equalTo: h.contentView.leadingAnchor).isActive = true
        v.trailingAnchor.constraint(equalTo: h.contentView.trailingAnchor).isActive = true
        h.content = v
        // other initializations for all headers go here
    }
    h.content.lab.text = // whatever
    // other initializations for this header go here
    return h
}

それは恐ろしくて迷惑ですが、できる限りのことです。

31
matt

Matt answerにコメントを追加するほどの評判はありません。

とにかく、ここで欠けている唯一のものは、新しいビューを追加する前に、UITableViewHeaderFooterView.contentViewからすべてのサブビューを削除することです。これにより、再利用されたセルが初期状態にリセットされ、メモリリークが回避されます。

2
copied