環境 Xcode9.3 Swift4.1です。

CollectionView で Headerの実装 + SearchBarの可変

ViewControllerを使用 StoryBordは使用してない Xibを使用

各メソッド毎にFileを分けて、プロトコルを明示しています。

挙動

CollectionView.gif

AppDelegate

StoryBordなしのため、コードでrootViewControllerを設定しています。

var window: UIWindow?
    fileprivate lazy var viewController: ViewController = ViewController()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.window?.backgroundColor = UIColor.white
        self.window?.rootViewController = viewController
        self.window?.makeKeyAndVisible()

        return true
    }

UIViewController

CollectionViewにプロパティを設定しています。
Collectionで複数のプロパティ設定をする場合は、
UICollectionViewDelegateFlowLayoutでメソッドを用意します。
Collectionも多数のメソッドが存在するので、extensionで各プロトコル毎に管理をお勧めします。

Command + F で検索しください。
insetForSectionAt-> UIEdgeInsetsの余白設定
sizeForItemAt-> Cellの大きさ
minimumInteritemSpacingForSectionAt-> セクション、列の余白設定
referenceSizeForHeaderInSection-> HedaerのSize設定

CollectionViewModelにCollectionViewのメソッドを実装しています。
NSObjectではoverrideは必要ありません。

import UIKit

final class ViewController: UIViewController {

    let vm = CollectionViewModel()
    lazy var cView: CollectionView = {
        let cView = CollectionView()
        cView.collectionView.delegate = self
        cView.collectionView.dataSource = self.vm
        self.view.addSubview(cView)
        return cView
    }()
    var margin: CGFloat = 10

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

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        cView.frame = view.bounds
    }

}

// MARK: UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate {}

// MARK: UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        if section == 0 {
            return UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
        } else {
            margin = 50
            return UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        if indexPath.section == 0 {
            margin = 80
            return CGSize(width: self.view.frame.width - margin, height: margin)
        } else {
            margin = 100
            return CGSize(width: self.view.frame.width - margin, height: margin)
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        if section == 0 {
            return margin
        } else {
            return margin
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        if section == 0 {
            return CGSize(width: UIScreen.main.bounds.width, height: 100)
        } else {
            return CGSize.zero
        }
    }
}

UICollectionView

UIView内でUICollectionViewのプロパティを設定しています。
UICollectionViewFlowLayout()をプロパティで設定しているので、メソッド実装していません。
プロパティはググって調べてください。

registerメソッドで、headerとforCellWithReuseIdentifierの登録をしています。
scrollViewも今回の要件ではないですが、拡張する場合に使うので、実装しました。

import UIKit

final class CollectionView: UIView {

    lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: UIScreen.main.bounds.width,
                                 height: 88)
        layout.headerReferenceSize = CGSize(width: self.bounds.width,
                                      height: 150)

        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.visibleSupplementaryViews(ofKind: UICollectionElementKindSectionHeader)
        collectionView.register(HeaderView.nib(), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderView.identifier)
        collectionView.register(CostomCell.nib(), forCellWithReuseIdentifier: CostomCell.identifier)
        collectionView.backgroundColor = .clear
        return collectionView
    }()
    lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        return scrollView
    }()

    init() {
        super.init(frame: .zero)
        addSubview(scrollView)
        scrollView.addSubview(collectionView)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        self.backgroundColor = .white
        collectionView.frame = self.frame
        scrollView.frame = self.frame
        scrollView.contentSize.height = self.frame.height + 100
    }

}

CollectionViewModel

cell、セクションの数、Headerの設定、Cellの設定をしています。
viewForSupplementaryElementOfKindと記載されているメソッドがheaderの設定です。

os10,os11で共通のSearchBarを作成するgetImageSizeメソッドを追加しました。
指定されたオプションでビットマップベースのグラフィックスコンテキストを作成して、ビットマップベースのグラフィックスコンテキストの内容に基づいてイメージを返します。

オプションの解説 Apple Developer

import UIKit

final class CollectionViewModel: NSObject {

    func getImageSize(size: CGSize) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

}

// MARK: UICollectionViewDataSource
extension CollectionViewModel: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath)
        return header
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CostomCell", for: indexPath) as! CostomCell
        return cell
    }

}

UICollectionReusableView

Headerを管理しているクラスです。XibファイルのプロパティをIBOutletで紐づけています。
textField?.backgroundColor = .clear
searchBar.backgroundImage = UIImage()
このメソッドでtextField,searchBar背景色を透明にしてから、searchBarの背景色を再度設定しています。
透明にしないと、デファルトのカラーが反映されます。
getImageSizeメソッドで、searchBar自体サイズ変更をして、searchBarの高さの可変を実現しています。

import UIKit

final class HeaderView: UICollectionReusableView {

    @IBOutlet weak var searchBar: UISearchBar! {
        didSet {
            let textField = searchBar.subviews.first?.subviews.flatMap { $0 as? UITextField }.first
            textField?.backgroundColor = .clear
            searchBar.backgroundImage = UIImage()
            searchBar.backgroundColor = .blue
            searchBar.layer.cornerRadius = 15
            let image = vm.getImageSize(size: CGSize(width: self.frame.width, height: 100))
            searchBar.setSearchFieldBackgroundImage(image, for: .normal)
        }
    }
    static let identifier: UINib = UINib(nibName: "HeaderView", bundle: nil)
    let vm = CollectionViewModel()

}

HeaderView.xib

スクリーンショット 2018-04-08 21.27.05.png

UICollectionViewCell

Cellを管理しているクラスです。XibファイルのプロパティをIBOutletで紐づけています。

import UIKit

final class CostomCell: UICollectionViewCell {

    @IBOutlet weak var costomCell: UIView!

    static let identifier: UINib = UINib(nibName: "CostomCell", bundle: nil)

}

CostomCell.xib

スクリーンショット 2018-03-21 13.12.35.png

ソースコード

CollectionViewHeader

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.