Quellcode durchsuchen

MediaSubcategoryViewController: adding swipable viewcontrollers for the Video and Audio subcategories

Carola Nitz vor 7 Jahren
Ursprung
Commit
aeef84bd30

+ 50 - 0
ButtonCell.xib

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14092" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14081.1"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </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="zg4-fX-zUF">
+            <rect key="frame" x="0.0" y="0.0" width="80" height="40"/>
+            <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="80" height="40"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s7f-rk-sQl">
+                        <rect key="frame" x="22" y="12" width="37.5" height="17"/>
+                        <fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
+                        <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="tFG-yJ-6Th">
+                        <rect key="frame" x="22.5" y="2.5" width="35" height="35"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="35" id="VRw-5F-5WY"/>
+                            <constraint firstAttribute="height" constant="35" id="ojk-Ug-Lgh"/>
+                        </constraints>
+                    </imageView>
+                </subviews>
+            </view>
+            <color key="backgroundColor" red="0.027450980390000001" green="0.72549019609999998" blue="0.60784313729999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="tFG-yJ-6Th" firstAttribute="centerY" secondItem="zg4-fX-zUF" secondAttribute="centerY" id="5so-ZP-gRs"/>
+                <constraint firstItem="tFG-yJ-6Th" firstAttribute="centerX" secondItem="zg4-fX-zUF" secondAttribute="centerX" id="9em-NR-hoa"/>
+                <constraint firstItem="s7f-rk-sQl" firstAttribute="centerY" secondItem="zg4-fX-zUF" secondAttribute="centerY" id="ZeP-6I-AXE"/>
+                <constraint firstItem="s7f-rk-sQl" firstAttribute="centerX" secondItem="zg4-fX-zUF" secondAttribute="centerX" id="c15-bZ-hPG"/>
+            </constraints>
+            <connections>
+                <outlet property="imageView" destination="tFG-yJ-6Th" id="Odb-dR-tf4"/>
+                <outlet property="label" destination="s7f-rk-sQl" id="4gU-tb-BMB"/>
+            </connections>
+            <point key="canvasLocation" x="307" y="541"/>
+        </collectionViewCell>
+    </objects>
+</document>

+ 55 - 0
IconLabelCell.xib

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14092" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14081.1"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </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="2CI-9N-dP3" userLabel="Icon Label Cell" customClass="IconLabelCell" customModule="VLC_iOS" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="74" height="70"/>
+            <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="74" height="70"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Tkn-VR-o25">
+                        <rect key="frame" x="20" y="10" width="35" height="35"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="35" id="EUF-rI-9Y8"/>
+                            <constraint firstAttribute="height" constant="35" id="RoA-LT-AHV"/>
+                        </constraints>
+                    </imageView>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rwk-fy-4KB">
+                        <rect key="frame" x="0.0" y="45" width="74" height="20"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="20" id="kWi-HY-P6M"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" pointSize="10"/>
+                        <nil key="textColor"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                </subviews>
+            </view>
+            <constraints>
+                <constraint firstItem="rwk-fy-4KB" firstAttribute="top" secondItem="Tkn-VR-o25" secondAttribute="bottom" id="A8G-Sk-d4C"/>
+                <constraint firstItem="Tkn-VR-o25" firstAttribute="top" secondItem="2CI-9N-dP3" secondAttribute="top" constant="10" id="DaW-j3-w2g"/>
+                <constraint firstItem="rwk-fy-4KB" firstAttribute="leading" secondItem="2CI-9N-dP3" secondAttribute="leading" id="HFm-qh-fCk"/>
+                <constraint firstAttribute="bottom" secondItem="rwk-fy-4KB" secondAttribute="bottom" constant="5" id="Q0g-pb-bbf"/>
+                <constraint firstAttribute="trailing" secondItem="rwk-fy-4KB" secondAttribute="trailing" id="YuE-x9-9EY"/>
+                <constraint firstItem="Tkn-VR-o25" firstAttribute="centerX" secondItem="2CI-9N-dP3" secondAttribute="centerX" id="wav-eA-a2N"/>
+            </constraints>
+            <size key="customSize" width="74" height="50"/>
+            <connections>
+                <outlet property="iconImage" destination="Tkn-VR-o25" id="7DO-mm-BJX"/>
+                <outlet property="iconLabel" destination="rwk-fy-4KB" id="76s-nG-weE"/>
+            </connections>
+            <point key="canvasLocation" x="317" y="376"/>
+        </collectionViewCell>
+    </objects>
+</document>

+ 13 - 0
Resources/en.lproj/Localizable.strings

@@ -305,3 +305,16 @@
 "SORT" = "Sort";
 "SORT_BY" = "Sort by";
 "VIDEO" = "Video";
+
+/* New strings */
+"FOLDER_EMPTY" = "FOLDER_EMPTY";
+
+/* New strings */
+"albums" = "albums";
+"artists" = "artists";
+"genres" = "genres";
+"movies" = "movies";
+"playlists" = "playlists";
+"songs" = "songs";
+"videos" = "videos";
+"episodes" = "episodes";

+ 3 - 1
SharedSources/MediaDataSourceAndDelegate.swift

@@ -39,7 +39,9 @@ public class MediaDataSourceAndDelegate: NSObject, UICollectionViewDataSource, U
 
     public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
         if let playlistCell = collectionView.dequeueReusableCell(withReuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier(), for: indexPath) as? VLCPlaylistCollectionViewCell {
-            playlistCell.mediaObject = services.mediaDataSource.object(at: indexPath.row, subcategory: mediaType.subcategory)
+            if let mediaObject = services.mediaDataSource.object(at: indexPath.row, subcategory: mediaType.subcategory) as? NSManagedObject {
+                playlistCell.mediaObject = mediaObject
+            }
             return playlistCell
         }
         return UICollectionViewCell()

+ 903 - 0
Sources/BaseButtonBarPagerTabStripViewController.swift

@@ -0,0 +1,903 @@
+//
+//  BaseButtonBarPagerTabStripViewController.swift
+//  VLC-iOS
+//
+//  Created by Carola Nitz on 5/3/18.
+//  Copyright © 2018 VideoLAN. All rights reserved.
+//
+
+import Foundation
+
+
+class IconLabelCell: UICollectionViewCell {
+
+    @IBOutlet weak var iconImage: UIImageView!
+    @IBOutlet weak var iconLabel: UILabel!
+
+}
+public enum SwipeDirection {
+    case left
+    case right
+    case none
+}
+
+public struct ButtonBarPagerTabStripSettings {
+
+    public struct Style {
+        public var buttonBarBackgroundColor: UIColor?
+        public var buttonBarMinimumInteritemSpacing: CGFloat?
+        public var buttonBarMinimumLineSpacing: CGFloat?
+
+        public var selectedBarBackgroundColor = UIColor.black
+        public var selectedBarHeight: CGFloat = 5
+        public var selectedBarVerticalAlignment: SelectedBarVerticalAlignment = .bottom
+
+        public var buttonBarItemFont = UIFont.systemFont(ofSize: 18)
+        public var buttonBarItemLeftRightMargin: CGFloat = 8
+        public var buttonBarItemTitleColor: UIColor?
+        public var buttonBarItemsShouldFillAvailableWidth = true
+        // only used if button bar is created programaticaly and not using storyboards or nib files
+        public var buttonBarHeight: CGFloat?
+    }
+
+    public var style = Style()
+}
+
+public struct IndicatorInfo {
+
+    public var title: String?
+    public var image: UIImage?
+    public var accessibilityLabel: String?
+
+    public init(title: String?, image: UIImage?) {
+        self.title = title
+        self.accessibilityLabel = title
+        self.image = image
+    }
+
+}
+
+public enum ButtonBarItemSpec<CellType: UICollectionViewCell> {
+
+    case nibFile(nibName: String, bundle: Bundle?, width:((IndicatorInfo)-> CGFloat))
+    case cellClass(width:((IndicatorInfo)-> CGFloat))
+
+    public var weight: ((IndicatorInfo) -> CGFloat) {
+        switch self {
+        case .cellClass(let widthCallback):
+            return widthCallback
+        case .nibFile(_, _, let widthCallback):
+            return widthCallback
+        }
+    }
+}
+public enum PagerScroll {
+    case no
+    case yes
+    case scrollOnlyIfOutOfScreen
+}
+
+public enum SelectedBarAlignment {
+    case left
+    case center
+    case right
+    case progressive
+}
+
+public enum SelectedBarVerticalAlignment {
+    case top
+    case middle
+    case bottom
+}
+
+open class ButtonBarView: UICollectionView {
+
+    open lazy var selectedBar: UIView = { [unowned self] in
+        let bar  = UIView(frame: CGRect(x: 0, y: self.frame.size.height - CGFloat(self.selectedBarHeight), width: 0, height: CGFloat(self.selectedBarHeight)))
+        bar.layer.zPosition = 9999
+        return bar
+        }()
+
+    internal var selectedBarHeight: CGFloat = 4 {
+        didSet {
+            updateSelectedBarYPosition()
+        }
+    }
+    var selectedBarVerticalAlignment: SelectedBarVerticalAlignment = .bottom
+    var selectedBarAlignment: SelectedBarAlignment = .center
+    var selectedIndex = 0
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        addSubview(selectedBar)
+    }
+
+    public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
+        super.init(frame: frame, collectionViewLayout: layout)
+        addSubview(selectedBar)
+    }
+
+    open func moveTo(index: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
+        selectedIndex = index
+        updateSelectedBarPosition(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll)
+    }
+
+    open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat, pagerScroll: PagerScroll) {
+        selectedIndex = progressPercentage > 0.5 ? toIndex : fromIndex
+
+        let fromFrame = layoutAttributesForItem(at: IndexPath(item: fromIndex, section: 0))!.frame
+        let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
+
+        var toFrame: CGRect
+
+        if toIndex < 0 || toIndex > numberOfItems - 1 {
+            if toIndex < 0 {
+                let cellAtts = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
+                toFrame = cellAtts!.frame.offsetBy(dx: -cellAtts!.frame.size.width, dy: 0)
+            } else {
+                let cellAtts = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: 0))
+                toFrame = cellAtts!.frame.offsetBy(dx: cellAtts!.frame.size.width, dy: 0)
+            }
+        } else {
+            toFrame = layoutAttributesForItem(at: IndexPath(item: toIndex, section: 0))!.frame
+        }
+
+        var targetFrame = fromFrame
+        targetFrame.size.height = selectedBar.frame.size.height
+        targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage
+        targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage
+
+        selectedBar.frame = CGRect(x: targetFrame.origin.x, y: selectedBar.frame.origin.y, width: targetFrame.size.width, height: selectedBar.frame.size.height)
+
+        var targetContentOffset: CGFloat = 0.0
+        if contentSize.width > frame.size.width {
+            let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex)
+            let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex)
+
+            targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage)
+        }
+
+        setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: false)
+    }
+
+    open func updateSelectedBarPosition(_ animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
+        var selectedBarFrame = selectedBar.frame
+
+        let selectedCellIndexPath = IndexPath(item: selectedIndex, section: 0)
+        let attributes = layoutAttributesForItem(at: selectedCellIndexPath)
+        let selectedCellFrame = attributes!.frame
+
+        updateContentOffset(animated: animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: (selectedCellIndexPath as NSIndexPath).row)
+
+        selectedBarFrame.size.width = selectedCellFrame.size.width
+        selectedBarFrame.origin.x = selectedCellFrame.origin.x
+
+        if animated {
+            UIView.animate(withDuration: 0.3, animations: { [weak self] in
+                self?.selectedBar.frame = selectedBarFrame
+            })
+        } else {
+            selectedBar.frame = selectedBarFrame
+        }
+    }
+
+    // MARK: - Helpers
+
+    private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) {
+        guard pagerScroll != .no || (pagerScroll != .scrollOnlyIfOutOfScreen && (toFrame.origin.x < contentOffset.x || toFrame.origin.x >= (contentOffset.x + frame.size.width - contentInset.left))) else { return }
+        let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0
+        setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: animated)
+    }
+
+    private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat {
+        let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset // swiftlint:disable:this force_cast
+        var alignmentOffset: CGFloat = 0.0
+
+        switch selectedBarAlignment {
+        case .left:
+            alignmentOffset = sectionInset.left
+        case .right:
+            alignmentOffset = frame.size.width - sectionInset.right - cellFrame.size.width
+        case .center:
+            alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5
+        case .progressive:
+            let cellHalfWidth = cellFrame.size.width * 0.5
+            let leftAlignmentOffset = sectionInset.left + cellHalfWidth
+            let rightAlignmentOffset = frame.size.width - sectionInset.right - cellHalfWidth
+            let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
+            let progress = index / (numberOfItems - 1)
+            alignmentOffset = leftAlignmentOffset + (rightAlignmentOffset - leftAlignmentOffset) * CGFloat(progress) - cellHalfWidth
+        }
+
+        var contentOffset = cellFrame.origin.x - alignmentOffset
+        contentOffset = max(0, contentOffset)
+        contentOffset = min(contentSize.width - frame.size.width, contentOffset)
+        return contentOffset
+    }
+
+    private func updateSelectedBarYPosition() {
+        var selectedBarFrame = selectedBar.frame
+
+        switch selectedBarVerticalAlignment {
+        case .top:
+            selectedBarFrame.origin.y = 0
+        case .middle:
+            selectedBarFrame.origin.y = (frame.size.height - selectedBarHeight) / 2
+        case .bottom:
+            selectedBarFrame.origin.y = frame.size.height - selectedBarHeight
+        }
+
+        selectedBarFrame.size.height = selectedBarHeight
+        selectedBar.frame = selectedBarFrame
+    }
+
+    override open func layoutSubviews() {
+        super.layoutSubviews()
+        updateSelectedBarYPosition()
+    }
+}
+
+open class BaseButtonBarPagerTabStripViewController<ButtonBarCellType: UICollectionViewCell>: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate, UICollectionViewDelegate, UICollectionViewDataSource {
+
+    public var settings = ButtonBarPagerTabStripSettings()
+    public var buttonBarItemSpec: ButtonBarItemSpec<ButtonBarCellType>!
+    public var changeCurrentIndex: ((_ oldCell: ButtonBarCellType?, _ newCell: ButtonBarCellType?, _ animated: Bool) -> Void)?
+    public var changeCurrentIndexProgressive: ((_ oldCell: ButtonBarCellType?, _ newCell: ButtonBarCellType?, _ progressPercentage: CGFloat, _ changeCurrentIndex: Bool, _ animated: Bool) -> Void)?
+
+    @IBOutlet public weak var buttonBarView: ButtonBarView!
+
+    lazy private var cachedCellWidths: [CGFloat]? = { [unowned self] in
+        return self.calculateWidths()
+        }()
+
+    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+        delegate = self
+        datasource = self
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        delegate = self
+        datasource = self
+    }
+
+    open override func viewDidLoad() {
+        super.viewDidLoad()
+
+        let buttonBarViewAux = buttonBarView ?? {
+            let flowLayout = UICollectionViewFlowLayout()
+            flowLayout.scrollDirection = .horizontal
+            let buttonBarHeight = settings.style.buttonBarHeight ?? 44
+            let yPos: CGFloat;
+            if #available(iOS 11.0, *) {
+                yPos = navigationController!.navigationBar.safeAreaInsets.top + navigationController!.navigationBar.frame.height
+            } else {
+                yPos = navigationController!.navigationBar.frame.maxY
+            }
+            let buttonBar = ButtonBarView(frame: CGRect(x: 0, y: yPos, width: view.frame.size.width, height: buttonBarHeight), collectionViewLayout: flowLayout)
+            buttonBar.backgroundColor = .orange
+            buttonBar.selectedBar.backgroundColor = .black
+            buttonBar.autoresizingMask = .flexibleWidth
+            var newContainerViewFrame = containerView.frame
+            newContainerViewFrame.origin.y = buttonBar.frame.maxY
+            newContainerViewFrame.size.height = containerView.frame.size.height - (buttonBar.frame.maxY - containerView.frame.origin.y)
+            containerView.frame = newContainerViewFrame
+            return buttonBar
+            }()
+        buttonBarView = buttonBarViewAux
+
+        if buttonBarView.superview == nil {
+            view.addSubview(buttonBarView)
+        }
+        if buttonBarView.delegate == nil {
+            buttonBarView.delegate = self
+        }
+        if buttonBarView.dataSource == nil {
+            buttonBarView.dataSource = self
+        }
+        buttonBarView.scrollsToTop = false
+        let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
+        flowLayout.scrollDirection = .horizontal
+        flowLayout.minimumInteritemSpacing = settings.style.buttonBarMinimumInteritemSpacing ?? flowLayout.minimumInteritemSpacing
+        flowLayout.minimumLineSpacing = settings.style.buttonBarMinimumLineSpacing ?? flowLayout.minimumLineSpacing
+        buttonBarView.showsHorizontalScrollIndicator = false
+        buttonBarView.backgroundColor = settings.style.buttonBarBackgroundColor ?? buttonBarView.backgroundColor
+        buttonBarView.selectedBar.backgroundColor = settings.style.selectedBarBackgroundColor
+
+        buttonBarView.selectedBarHeight = settings.style.selectedBarHeight
+        // register button bar item cell
+        switch buttonBarItemSpec! {
+        case .nibFile(let nibName, let bundle, _):
+            buttonBarView.register(UINib(nibName: nibName, bundle: bundle), forCellWithReuseIdentifier:"Cell")
+        case .cellClass:
+            buttonBarView.register(ButtonBarCellType.self, forCellWithReuseIdentifier:"Cell")
+        }
+    }
+
+    open override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        buttonBarView.layoutIfNeeded()
+        isViewAppearing = true
+    }
+
+    open override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        isViewAppearing = false
+    }
+
+    open override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+
+        guard isViewAppearing || isViewRotating else { return }
+
+        // Force the UICollectionViewFlowLayout to get laid out again with the new size if
+        // a) The view is appearing.  This ensures that
+        //    collectionView:layout:sizeForItemAtIndexPath: is called for a second time
+        //    when the view is shown and when the view *frame(s)* are actually set
+        //    (we need the view frame's to have been set to work out the size's and on the
+        //    first call to collectionView:layout:sizeForItemAtIndexPath: the view frame(s)
+        //    aren't set correctly)
+        // b) The view is rotating.  This ensures that
+        //    collectionView:layout:sizeForItemAtIndexPath: is called again and can use the views
+        //    *new* frame so that the buttonBarView cell's actually get resized correctly
+        cachedCellWidths = calculateWidths()
+        buttonBarView.collectionViewLayout.invalidateLayout()
+        // When the view first appears or is rotated we also need to ensure that the barButtonView's
+        // selectedBar is resized and its contentOffset/scroll is set correctly (the selected
+        // tab/cell may end up either skewed or off screen after a rotation otherwise)
+        buttonBarView.moveTo(index: currentIndex, animated: false, swipeDirection: .none, pagerScroll: .scrollOnlyIfOutOfScreen)
+        buttonBarView.selectItem(at: IndexPath(item: currentIndex, section: 0), animated: false, scrollPosition: [])
+    }
+
+    // MARK: - View Rotation
+
+    open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
+        super.viewWillTransition(to: size, with: coordinator)
+        let yPos: CGFloat;
+        if #available(iOS 11.0, *) {
+            yPos = navigationController!.navigationBar.safeAreaInsets.top + navigationController!.navigationBar.frame.height
+        } else {
+            yPos = navigationController!.navigationBar.frame.maxY
+        }
+        if let buttonBarView = buttonBarView {
+            buttonBarView.frame  = CGRect(x: 0, y: yPos, width: view.frame.size.width, height: buttonBarView.frame.height)
+            var newContainerViewFrame = containerView.frame
+            newContainerViewFrame.origin.y = buttonBarView.frame.maxY
+            newContainerViewFrame.size.height = containerView.frame.size.height - (buttonBarView.frame.maxY - containerView.frame.origin.y)
+            containerView.frame = newContainerViewFrame
+        }
+    }
+
+    // MARK: - Public Methods
+
+    open override func reloadPagerTabStripView() {
+        super.reloadPagerTabStripView()
+        guard isViewLoaded else { return }
+        buttonBarView.reloadData()
+        cachedCellWidths = calculateWidths()
+        buttonBarView.moveTo(index: currentIndex, animated: false, swipeDirection: .none, pagerScroll: .yes)
+    }
+
+    open func calculateStretchedCellWidths(_ minimumCellWidths: [CGFloat], suggestedStretchedCellWidth: CGFloat, previousNumberOfLargeCells: Int) -> CGFloat {
+        var numberOfLargeCells = 0
+        var totalWidthOfLargeCells: CGFloat = 0
+
+        for minimumCellWidthValue in minimumCellWidths where minimumCellWidthValue > suggestedStretchedCellWidth {
+            totalWidthOfLargeCells += minimumCellWidthValue
+            numberOfLargeCells += 1
+        }
+
+        guard numberOfLargeCells > previousNumberOfLargeCells else { return suggestedStretchedCellWidth }
+
+        let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
+        let collectionViewAvailiableWidth = buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right
+        let numberOfCells = minimumCellWidths.count
+        let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumLineSpacing
+
+        let numberOfSmallCells = numberOfCells - numberOfLargeCells
+        let newSuggestedStretchedCellWidth = (collectionViewAvailiableWidth - totalWidthOfLargeCells - cellSpacingTotal) / CGFloat(numberOfSmallCells)
+
+        return calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: newSuggestedStretchedCellWidth, previousNumberOfLargeCells: numberOfLargeCells)
+    }
+
+    open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
+        guard shouldUpdateButtonBarView else { return }
+        buttonBarView.moveTo(index: toIndex, animated: true, swipeDirection: toIndex < fromIndex ? .right : .left, pagerScroll: .yes)
+
+        if let changeCurrentIndex = changeCurrentIndex {
+            let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex != fromIndex ? fromIndex : toIndex, section: 0)) as? ButtonBarCellType
+            let newCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
+            changeCurrentIndex(oldCell, newCell, true)
+        }
+    }
+
+    open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
+        guard shouldUpdateButtonBarView else { return }
+        buttonBarView.move(fromIndex: fromIndex, toIndex: toIndex, progressPercentage: progressPercentage, pagerScroll: .yes)
+        if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
+            let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex != fromIndex ? fromIndex : toIndex, section: 0)) as? ButtonBarCellType
+            let newCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
+            changeCurrentIndexProgressive(oldCell, newCell, progressPercentage, indexWasChanged, true)
+        }
+    }
+
+    // MARK: - UICollectionViewDelegateFlowLayut
+
+    @objc open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
+        guard let cellWidthValue = cachedCellWidths?[indexPath.row] else {
+            fatalError("cachedCellWidths for \(indexPath.row) must not be nil")
+        }
+        return CGSize(width: cellWidthValue, height: collectionView.frame.size.height)
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        guard indexPath.item != currentIndex else { return }
+
+        buttonBarView.moveTo(index: indexPath.item, animated: true, swipeDirection: .none, pagerScroll: .yes)
+        shouldUpdateButtonBarView = false
+
+        let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
+        let newCell = buttonBarView.cellForItem(at: IndexPath(item: indexPath.item, section: 0)) as? ButtonBarCellType
+
+        if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
+            changeCurrentIndexProgressive(oldCell, newCell, 1, true, true)
+        }
+        moveToViewController(at: indexPath.item)
+    }
+
+    // MARK: - UICollectionViewDataSource
+
+    open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return viewControllers.count
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? ButtonBarCellType else {
+            fatalError("UICollectionViewCell should be or extend from ButtonBarViewCell")
+        }
+        let childController = viewControllers[indexPath.item] as! IndicatorInfoProvider // swiftlint:disable:this force_cast
+        let indicatorInfo = childController.indicatorInfo(for: self)
+
+        configure(cell: cell, for: indicatorInfo)
+
+        if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
+            changeCurrentIndexProgressive(currentIndex == indexPath.item ? nil : cell, currentIndex == indexPath.item ? cell : nil, 1, true, false)
+        }
+
+        return cell
+    }
+
+    // MARK: - UIScrollViewDelegate
+
+    open override func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
+        super.scrollViewDidEndScrollingAnimation(scrollView)
+
+        guard scrollView == containerView else { return }
+        shouldUpdateButtonBarView = true
+    }
+
+    open func configure(cell: ButtonBarCellType, for indicatorInfo: IndicatorInfo) {
+        fatalError("You must override this method to set up ButtonBarView cell accordingly")
+    }
+
+    private func calculateWidths() -> [CGFloat] {
+        let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
+        let numberOfCells = viewControllers.count
+
+        var minimumCellWidths = [CGFloat]()
+        var collectionViewContentWidth: CGFloat = 0
+
+        for viewController in viewControllers {
+            let childController = viewController as! IndicatorInfoProvider // swiftlint:disable:this force_cast
+            let indicatorInfo = childController.indicatorInfo(for: self)
+            switch buttonBarItemSpec! {
+            case .cellClass(let widthCallback):
+                let width = widthCallback(indicatorInfo)
+                minimumCellWidths.append(width)
+                collectionViewContentWidth += width
+            case .nibFile(_, _, let widthCallback):
+                let width = widthCallback(indicatorInfo)
+                minimumCellWidths.append(width)
+                collectionViewContentWidth += width
+            }
+        }
+
+        let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumLineSpacing
+        collectionViewContentWidth += cellSpacingTotal
+
+        let collectionViewAvailableVisibleWidth = buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right
+
+        if !settings.style.buttonBarItemsShouldFillAvailableWidth || collectionViewAvailableVisibleWidth < collectionViewContentWidth {
+            return minimumCellWidths
+        } else {
+            let stretchedCellWidthIfAllEqual = (collectionViewAvailableVisibleWidth - cellSpacingTotal) / CGFloat(numberOfCells)
+            let generalMinimumCellWidth = calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: stretchedCellWidthIfAllEqual, previousNumberOfLargeCells: 0)
+            var stretchedCellWidths = [CGFloat]()
+
+            for minimumCellWidthValue in minimumCellWidths {
+                let cellWidth = (minimumCellWidthValue > generalMinimumCellWidth) ? minimumCellWidthValue : generalMinimumCellWidth
+                stretchedCellWidths.append(cellWidth)
+            }
+
+            return stretchedCellWidths
+        }
+    }
+
+    private var shouldUpdateButtonBarView = true
+}
+
+// MARK: Protocols
+
+public protocol IndicatorInfoProvider {
+
+    func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo
+
+}
+
+public protocol PagerTabStripDelegate: class {
+
+    func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int)
+}
+
+public protocol PagerTabStripIsProgressiveDelegate: PagerTabStripDelegate {
+
+    func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool)
+}
+
+public protocol PagerTabStripDataSource: class {
+
+    func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController]
+}
+
+// MARK: PagerTabStripViewController
+
+open class PagerTabStripViewController: UIViewController, UIScrollViewDelegate {
+
+    @IBOutlet weak public var containerView: UIScrollView!
+
+    open weak var delegate: PagerTabStripDelegate?
+    open weak var datasource: PagerTabStripDataSource?
+
+    open private(set) var viewControllers = [UIViewController]()
+    open private(set) var currentIndex = 0
+    open private(set) var preCurrentIndex = 0 // used *only* to store the index to which move when the pager becomes visible
+
+    open var pageWidth: CGFloat {
+        return containerView.bounds.width
+    }
+
+    open var scrollPercentage: CGFloat {
+        if swipeDirection != .right {
+            let module = fmod(containerView.contentOffset.x, pageWidth)
+            return module == 0.0 ? 1.0 : module / pageWidth
+        }
+        return 1 - fmod(containerView.contentOffset.x >= 0 ? containerView.contentOffset.x : pageWidth + containerView.contentOffset.x, pageWidth) / pageWidth
+    }
+
+    open var swipeDirection: SwipeDirection {
+        if containerView.contentOffset.x > lastContentOffset {
+            return .left
+        } else if containerView.contentOffset.x < lastContentOffset {
+            return .right
+        }
+        return .none
+    }
+
+    override open func viewDidLoad() {
+        super.viewDidLoad()
+        let containerViewAux = containerView ?? {
+            let containerView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))
+            containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+            return containerView
+            }()
+        containerView = containerViewAux
+        if containerView.superview == nil {
+            view.addSubview(containerView)
+        }
+        containerView.bounces = true
+        containerView.alwaysBounceHorizontal = true
+        containerView.alwaysBounceVertical = false
+        containerView.scrollsToTop = false
+        containerView.delegate = self
+        containerView.showsVerticalScrollIndicator = false
+        containerView.showsHorizontalScrollIndicator = false
+        containerView.isPagingEnabled = true
+        containerView.backgroundColor = PresentationTheme.current.colors.background
+        reloadViewControllers()
+
+        let childController = viewControllers[currentIndex]
+        addChildViewController(childController)
+        childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
+        containerView.addSubview(childController.view)
+        childController.didMove(toParentViewController: self)
+    }
+
+    open override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        isViewAppearing = true
+        childViewControllers.forEach { $0.beginAppearanceTransition(true, animated: animated) }
+    }
+
+    override open func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        lastSize = containerView.bounds.size
+        updateIfNeeded()
+        let needToUpdateCurrentChild = preCurrentIndex != currentIndex
+        if needToUpdateCurrentChild {
+            moveToViewController(at: preCurrentIndex)
+        }
+        isViewAppearing = false
+        childViewControllers.forEach { $0.endAppearanceTransition() }
+    }
+
+    open override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        childViewControllers.forEach { $0.beginAppearanceTransition(false, animated: animated) }
+    }
+
+    open override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        childViewControllers.forEach { $0.endAppearanceTransition() }
+    }
+
+    override open func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+        updateIfNeeded()
+    }
+
+    open override var shouldAutomaticallyForwardAppearanceMethods: Bool {
+        return false
+    }
+
+    open func moveToViewController(at index: Int, animated: Bool = true) {
+        guard isViewLoaded && view.window != nil && currentIndex != index else {
+            preCurrentIndex = index
+            return
+        }
+
+        if animated && abs(currentIndex - index) > 1 {
+            var tmpViewControllers = viewControllers
+            let currentChildVC = viewControllers[currentIndex]
+            let fromIndex = currentIndex < index ? index - 1 : index + 1
+            let fromChildVC = viewControllers[fromIndex]
+            tmpViewControllers[currentIndex] = fromChildVC
+            tmpViewControllers[fromIndex] = currentChildVC
+            pagerTabStripChildViewControllersForScrolling = tmpViewControllers
+            containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: fromIndex), y: 0), animated: false)
+            (navigationController?.view ?? view).isUserInteractionEnabled = !animated
+            containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: index), y: 0), animated: true)
+        } else {
+            (navigationController?.view ?? view).isUserInteractionEnabled = !animated
+            containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: index), y: 0), animated: animated)
+        }
+    }
+
+    open func moveTo(viewController: UIViewController, animated: Bool = true) {
+        moveToViewController(at: viewControllers.index(of: viewController)!, animated: animated)
+    }
+
+    // MARK: - PagerTabStripDataSource
+
+    open func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
+        assertionFailure("Sub-class must implement the PagerTabStripDataSource viewControllers(for:) method")
+        return []
+    }
+
+    // MARK: - Helpers
+    public enum PagerTabStripError: Error {
+
+        case viewControllerOutOfBounds
+
+    }
+    
+    open func updateIfNeeded() {
+        if isViewLoaded && !lastSize.equalTo(containerView.bounds.size) {
+            updateContent()
+        }
+    }
+
+    open func canMoveTo(index: Int) -> Bool {
+        return currentIndex != index && viewControllers.count > index
+    }
+
+    open func pageOffsetForChild(at index: Int) -> CGFloat {
+        return CGFloat(index) * containerView.bounds.width
+    }
+
+    open func offsetForChild(at index: Int) -> CGFloat {
+        return (CGFloat(index) * containerView.bounds.width) + ((containerView.bounds.width - view.bounds.width) * 0.5)
+    }
+
+    open func offsetForChild(viewController: UIViewController) throws -> CGFloat {
+        guard let index = viewControllers.index(of: viewController) else {
+            throw PagerTabStripError.viewControllerOutOfBounds
+        }
+        return offsetForChild(at: index)
+    }
+
+    open func pageFor(contentOffset: CGFloat) -> Int {
+        let result = virtualPageFor(contentOffset: contentOffset)
+        return pageFor(virtualPage: result)
+    }
+
+    open func virtualPageFor(contentOffset: CGFloat) -> Int {
+        return Int((contentOffset + 1.5 * pageWidth) / pageWidth) - 1
+    }
+
+    open func pageFor(virtualPage: Int) -> Int {
+        if virtualPage < 0 {
+            return 0
+        }
+        if virtualPage > viewControllers.count - 1 {
+            return viewControllers.count - 1
+        }
+        return virtualPage
+    }
+
+    open func updateContent() {
+        if lastSize.width != containerView.bounds.size.width {
+            lastSize = containerView.bounds.size
+            containerView.contentOffset = CGPoint(x: pageOffsetForChild(at: currentIndex), y: 0)
+        }
+        lastSize = containerView.bounds.size
+
+        let pagerViewControllers = pagerTabStripChildViewControllersForScrolling ?? viewControllers
+        containerView.contentSize = CGSize(width: containerView.bounds.width * CGFloat(pagerViewControllers.count), height: containerView.contentSize.height)
+
+        for (index, childController) in pagerViewControllers.enumerated() {
+            let pageOffsetForChild = self.pageOffsetForChild(at: index)
+            if fabs(containerView.contentOffset.x - pageOffsetForChild) < containerView.bounds.width {
+                if childController.parent != nil {
+                    childController.view.frame = CGRect(x: offsetForChild(at: index), y: 0, width: view.bounds.width, height: containerView.bounds.height)
+                    childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
+                } else {
+                    childController.beginAppearanceTransition(true, animated: false)
+                    addChildViewController(childController)
+                    childController.view.frame = CGRect(x: offsetForChild(at: index), y: 0, width: view.bounds.width, height: containerView.bounds.height)
+                    childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
+                    containerView.addSubview(childController.view)
+                    childController.didMove(toParentViewController: self)
+                    childController.endAppearanceTransition()
+                }
+            } else {
+                if childController.parent != nil {
+                    childController.beginAppearanceTransition(false, animated: false)
+                    childController.willMove(toParentViewController: nil)
+                    childController.view.removeFromSuperview()
+                    childController.removeFromParentViewController()
+                    childController.endAppearanceTransition()
+                }
+            }
+        }
+
+        let oldCurrentIndex = currentIndex
+        let virtualPage = virtualPageFor(contentOffset: containerView.contentOffset.x)
+        let newCurrentIndex = pageFor(virtualPage: virtualPage)
+        currentIndex = newCurrentIndex
+        preCurrentIndex = currentIndex
+        let changeCurrentIndex = newCurrentIndex != oldCurrentIndex
+
+        if let progressiveDelegate = self as? PagerTabStripIsProgressiveDelegate {
+
+            let (fromIndex, toIndex, scrollPercentage) = progressiveIndicatorData(virtualPage)
+            progressiveDelegate.updateIndicator(for: self, fromIndex: fromIndex, toIndex: toIndex, withProgressPercentage: scrollPercentage, indexWasChanged: changeCurrentIndex)
+        } else {
+            delegate?.updateIndicator(for: self, fromIndex: min(oldCurrentIndex, pagerViewControllers.count - 1), toIndex: newCurrentIndex)
+        }
+    }
+
+    open func reloadPagerTabStripView() {
+        guard isViewLoaded else { return }
+        for childController in viewControllers where childController.parent != nil {
+            childController.beginAppearanceTransition(false, animated: false)
+            childController.willMove(toParentViewController: nil)
+            childController.view.removeFromSuperview()
+            childController.removeFromParentViewController()
+            childController.endAppearanceTransition()
+        }
+        reloadViewControllers()
+        containerView.contentSize = CGSize(width: containerView.bounds.width * CGFloat(viewControllers.count), height: containerView.contentSize.height)
+        if currentIndex >= viewControllers.count {
+            currentIndex = viewControllers.count - 1
+        }
+        preCurrentIndex = currentIndex
+        containerView.contentOffset = CGPoint(x: pageOffsetForChild(at: currentIndex), y: 0)
+        updateContent()
+    }
+
+    // MARK: - UIScrollViewDelegate
+
+    open func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        if containerView == scrollView {
+            updateContent()
+            lastContentOffset = scrollView.contentOffset.x
+        }
+    }
+
+    open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+        if containerView == scrollView {
+            lastPageNumber = pageFor(contentOffset: scrollView.contentOffset.x)
+        }
+    }
+
+    open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
+        if containerView == scrollView {
+            pagerTabStripChildViewControllersForScrolling = nil
+            (navigationController?.view ?? view).isUserInteractionEnabled = true
+            updateContent()
+        }
+    }
+
+    // MARK: - Orientation
+
+    open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
+        super.viewWillTransition(to: size, with: coordinator)
+        isViewRotating = true
+        pageBeforeRotate = currentIndex
+        coordinator.animate(alongsideTransition: nil) { [weak self] _ in
+            guard let me = self else { return }
+            me.isViewRotating = false
+            me.currentIndex = me.pageBeforeRotate
+            me.preCurrentIndex = me.currentIndex
+            me.updateIfNeeded()
+        }
+    }
+
+    // MARK: Private
+
+    private func progressiveIndicatorData(_ virtualPage: Int) -> (Int, Int, CGFloat) {
+        let count = viewControllers.count
+        var fromIndex = currentIndex
+        var toIndex = currentIndex
+        let direction = swipeDirection
+
+        if direction == .left {
+            if virtualPage > count - 1 {
+                fromIndex = count - 1
+                toIndex = count
+            } else {
+                if self.scrollPercentage >= 0.5 {
+                    fromIndex = max(toIndex - 1, 0)
+                } else {
+                    toIndex = fromIndex + 1
+                }
+            }
+        } else if direction == .right {
+            if virtualPage < 0 {
+                fromIndex = 0
+                toIndex = -1
+            } else {
+                if self.scrollPercentage > 0.5 {
+                    fromIndex = min(toIndex + 1, count - 1)
+                } else {
+                    toIndex = fromIndex - 1
+                }
+            }
+        }
+
+        return (fromIndex, toIndex, self.scrollPercentage)
+    }
+
+    private func reloadViewControllers() {
+        guard let dataSource = datasource else {
+            fatalError("dataSource must not be nil")
+        }
+        viewControllers = dataSource.viewControllers(for: self)
+        // viewControllers
+        guard !viewControllers.isEmpty else {
+            fatalError("viewControllers(for:) should provide at least one child view controller")
+        }
+        viewControllers.forEach { if !($0 is IndicatorInfoProvider) { fatalError("Every view controller provided by PagerTabStripDataSource's viewControllers(for:) method must conform to IndicatorInfoProvider") }}
+
+    }
+
+    private var pagerTabStripChildViewControllersForScrolling: [UIViewController]?
+    private var lastPageNumber = 0
+    private var lastContentOffset: CGFloat = 0.0
+    private var pageBeforeRotate = 0
+    private var lastSize = CGSize(width: 0, height: 0)
+    internal var isViewRotating = false
+    internal var isViewAppearing = false
+
+}

+ 96 - 0
Sources/MediaSubcategoryViewController.swift

@@ -0,0 +1,96 @@
+/*****************************************************************************
+ * MediaSubcategoryViewController.swift
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2018 VideoLAN. All rights reserved.
+ * $Id$
+ *
+ * Authors: Carola Nitz <caro # videolan.org>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+import UIKit
+
+class VLCVideoSubcategoryViewController: VLCMediaSubcategoryViewController
+{
+    override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
+        let movies = VLCMediaViewController(services: services, type: VLCMediaType(category: .video, subcategory: .allVideos))
+        let episodes = VLCMediaViewController(services: services, type: VLCMediaType(category: .video, subcategory: .episodes))
+        let playlists = VLCMediaViewController(services: services, type: VLCMediaType(category: .video, subcategory: .videoPlaylists))
+        return [movies, episodes, playlists]
+    }
+}
+
+class VLCAudioSubcategoryViewController: VLCMediaSubcategoryViewController
+{
+    override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
+        let tracks = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .tracks))
+        let genres = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .genres))
+        let artists = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .artists))
+        let albums = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .albums))
+        let playlists = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .audioPlaylists))
+        return [tracks, genres, artists, albums, playlists]
+    }
+}
+
+class VLCMediaSubcategoryViewController: BaseButtonBarPagerTabStripViewController<IconLabelCell> {
+
+    internal var services: Services
+
+    init(services: Services) {
+        self.services = services
+        super.init(nibName: nil, bundle: nil)
+        buttonBarItemSpec = ButtonBarItemSpec.nibFile(nibName: "IconLabelCell", bundle: Bundle.main, width: { _ in
+            return 70.0
+        })
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        // change selected bar color
+        settings.style.buttonBarBackgroundColor = .white
+        settings.style.selectedBarBackgroundColor = PresentationTheme.current.colors.orangeUI
+        settings.style.selectedBarHeight = 4.0
+        settings.style.buttonBarItemTitleColor = .black
+        settings.style.buttonBarItemsShouldFillAvailableWidth = true
+
+        changeCurrentIndexProgressive = { (oldCell: IconLabelCell?, newCell: IconLabelCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) in
+            guard changeCurrentIndex == true else { return }
+            oldCell?.iconImage.tintColor = PresentationTheme.current.colors.cellDetailTextColor
+            oldCell?.iconLabel.textColor = PresentationTheme.current.colors.cellDetailTextColor
+            newCell?.iconImage.tintColor = PresentationTheme.current.colors.orangeUI
+            newCell?.iconLabel.textColor = PresentationTheme.current.colors.orangeUI
+        }
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+        super.viewDidLoad()
+    }
+
+    // MARK: - PagerTabStripDataSource
+
+    override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
+        fatalError("this should only be used as subclass")
+    }
+
+    override func configure(cell: IconLabelCell, for indicatorInfo: IndicatorInfo) {
+        cell.iconImage.image = indicatorInfo.image?.withRenderingMode(.alwaysTemplate)
+        cell.iconLabel.text = indicatorInfo.title?.trimmingCharacters(in: .whitespacesAndNewlines)
+    }
+
+    override func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
+        super.updateIndicator(for: viewController, fromIndex: fromIndex, toIndex: toIndex, withProgressPercentage: progressPercentage, indexWasChanged: indexWasChanged)
+        if indexWasChanged && toIndex >= 0 && toIndex < viewControllers.count {
+            let child = viewControllers[toIndex] as! IndicatorInfoProvider
+            UIView.performWithoutAnimation({ [weak self] in
+                guard let me = self else { return }
+                me.navigationItem.leftBarButtonItem?.title =  child.indicatorInfo(for: me).title
+            })
+        }
+    }
+
+}

+ 8 - 2
Sources/MediaViewController.swift

@@ -18,7 +18,7 @@ import Foundation
     func mediaViewControllerDidSelectSort(_ mediaViewController: VLCMediaViewController)
 }
 
-public class VLCMediaViewController: UICollectionViewController, UISearchResultsUpdating, UISearchControllerDelegate {
+public class VLCMediaViewController: UICollectionViewController, UISearchResultsUpdating, UISearchControllerDelegate, IndicatorInfoProvider {
     private var services: Services
     private var mediaDataSourceAndDelegate: MediaDataSourceAndDelegate?
     private var searchController: UISearchController?
@@ -171,7 +171,9 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
     // MARK: - MediaDatasourceAndDelegate
 
     override public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-        delegate?.mediaViewControllerDidSelectMediaObject(self, mediaObject: services.mediaDataSource.object(at: indexPath.row, subcategory: mediaType.subcategory))
+        if let mediaObject = services.mediaDataSource.object(at: indexPath.row, subcategory: mediaType.subcategory) as? NSManagedObject {
+            delegate?.mediaViewControllerDidSelectMediaObject(self, mediaObject: mediaObject)
+        }
     }
 
     // MARK: - Search
@@ -188,4 +190,8 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
     public func didDismissSearchController(_ searchController: UISearchController) {
         collectionView?.dataSource = mediaDataSourceAndDelegate
     }
+
+    public func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
+        return  services.mediaDataSource.indicatorInfo(for:mediaType.subcategory)
+    }
 }

+ 9 - 8
Sources/VLCDragAndDropManager.swift

@@ -25,7 +25,7 @@ struct DropError: Error {
 
 @available(iOS 11.0, *)
 @objc protocol VLCDragAndDropManagerDelegate: NSObjectProtocol {
-    func dragAndDropManagerRequestsFile(manager: VLCDragAndDropManager, atIndexPath indexPath: IndexPath) -> AnyObject?
+    func dragAndDropManagerRequestsFile(manager: VLCDragAndDropManager, atIndexPath indexPath: IndexPath) -> Any?
     func dragAndDropManagerInsertItem(manager: VLCDragAndDropManager, item: NSManagedObject, atIndexPath indexPath: IndexPath)
     func dragAndDropManagerDeleteItem(manager: VLCDragAndDropManager, atIndexPath indexPath: IndexPath)
     func dragAndDropManagerRemoveFileFromFolder(manager: VLCDragAndDropManager, file: NSManagedObject)
@@ -343,7 +343,7 @@ class VLCDragAndDropManager: NSObject, UICollectionViewDragDelegate, UITableView
         return false
     }
 
-    private func fileIsCollection(file: AnyObject?) -> Bool {
+    private func fileIsCollection(file: Any?) -> Bool {
         let isFolder = file as? MLLabel != nil
         let isAlbum = file as? MLAlbum != nil
         let isShow = file as? MLShow != nil
@@ -352,8 +352,9 @@ class VLCDragAndDropManager: NSObject, UICollectionViewDragDelegate, UITableView
 
     private func fileIsCollection(atIndexPath indexPath: IndexPath?) -> Bool {
         if let indexPath = indexPath {
-            let file = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath)
-            return fileIsCollection(file: file)
+            if let file = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
+                return fileIsCollection(file:file)
+            }
         }
         return false
     }
@@ -375,7 +376,7 @@ class VLCDragAndDropManager: NSObject, UICollectionViewDragDelegate, UITableView
     ///
     /// - Parameter file: Can be of type MLAlbum, MLLabel or MLShow
     /// - Returns: An array of UIDragItems
-    private func dragItemsforCollection(file: AnyObject) -> [UIDragItem] {
+    private func dragItemsforCollection(file: Any) -> [UIDragItem] {
         var dragItems = [UIDragItem]()
         var set = Set<AnyHashable>()
         if let folder = file as? MLLabel {
@@ -403,9 +404,9 @@ class VLCDragAndDropManager: NSObject, UICollectionViewDragDelegate, UITableView
         return dragItems
     }
 
-    // Provides an item for other applications
-    private func dragItem(fromFile file: AnyObject) -> [UIDragItem] {
-        guard let file = mlFile(from: file), let path = file.url else {
+    //Provides an item for other applications
+    private func dragItem(fromFile file: Any) -> [UIDragItem] {
+        guard let file = mlFile(from: file as AnyObject), let path = file.url else {
             assert(false, "can't create a dragitem if there is no file or the file has no url")
             return []
         }

+ 2 - 1
Sources/VLCMediaData+VLCDragAndDrop.swift

@@ -14,7 +14,8 @@ import Foundation
 
 @available(iOS 11.0, *)
 extension VLCMediaDataSource: VLCDragAndDropManagerDelegate {
-    func dragAndDropManagerRequestsFile(manager: VLCDragAndDropManager, atIndexPath indexPath: IndexPath) -> AnyObject? {
+
+    func dragAndDropManagerRequestsFile(manager: VLCDragAndDropManager, atIndexPath indexPath: IndexPath) -> Any? {
         return object(at: indexPath.row, subcategory: manager.mediaType.subcategory)
     }
 

+ 113 - 34
Sources/VLCMediaDataSource.swift

@@ -42,14 +42,12 @@ struct VLCMediaType {
     var foundAudio = [MLFile]()
 
     var movies = [MLFile]()
-    var episodes = [MLFile]()
-    var artists = [MLFile]()
-    var albums = [MLFile]()
-    var tracks = [MLFile]() // might be just foundAudio
-    var genres = [MLFile]()
-    var audioPlaylist = [MLFile]()
-    var videoPlaylist = [MLFile]()
-    var allVideos = [MLFile]() // might be just foundVideo
+    var episodes = [MLShowEpisode]()
+    var artists = [String]()
+    var albums = [MLAlbum]()
+    var genres = [String]()
+    var audioPlaylist = [MLLabel]()
+    var videoPlaylist = [MLLabel]()
 
     override init() {
         super.init()
@@ -57,43 +55,66 @@ struct VLCMediaType {
         getAllAudio()
     }
 
-    @objc func numberOfFiles(subcategory: VLCMediaSubcategory) -> Int {
+
+    @objc
+    func numberOfFiles(subcategory: VLCMediaSubcategory) -> Int {
         return array(for: subcategory).count
     }
 
-    private func array(for subcategory: VLCMediaSubcategory) -> [MLFile] {
+    private func array(for subcategory: VLCMediaSubcategory ) -> [Any] {
         switch subcategory {
         case .unknown:
             preconditionFailure("No")
         case .movies:
-            preconditionFailure("TODO")
             return movies
         case .episodes:
-            preconditionFailure("TODO")
             return episodes
         case .artists:
-            preconditionFailure("TODO")
             return artists
         case .albums:
-            preconditionFailure("TODO")
             return albums
         case .tracks:
-            return tracks
+            return foundAudio
         case .genres:
-            preconditionFailure("TODO")
             return genres
         case .audioPlaylists:
-            preconditionFailure("TODO")
             return audioPlaylist
         case .videoPlaylists:
-            preconditionFailure("TODO")
+
             return videoPlaylist
         case .allVideos:
-            return allVideos
+            return foundVideos
         }
     }
 
-    @objc func object(at index: Int, subcategory: VLCMediaSubcategory) -> NSManagedObject {
+    func indicatorInfo(for subcategory: VLCMediaSubcategory) -> IndicatorInfo {
+        switch subcategory {
+        case .unknown:
+            preconditionFailure("No")
+        case .movies:
+            return IndicatorInfo(title: NSLocalizedString("movies", comment: ""), image: UIImage(named: "TVShowsIcon"))
+        case .episodes:
+            return IndicatorInfo(title: NSLocalizedString("episodes", comment: ""), image: UIImage(named: "episodes"))
+        case .artists:
+            return IndicatorInfo(title: NSLocalizedString("artists", comment: ""), image: UIImage(named: "artists"))
+        case .albums:
+             return IndicatorInfo(title: NSLocalizedString("albums", comment: ""), image: UIImage(named: "MusicAlbums"))
+        case .tracks:
+            return IndicatorInfo(title: NSLocalizedString("songs", comment: ""), image: UIImage(named: "songs"))
+        case .genres:
+            return IndicatorInfo(title: NSLocalizedString("genres", comment: ""), image: UIImage(named: "genres"))
+        case .audioPlaylists:
+            return IndicatorInfo(title: NSLocalizedString("playlists", comment: ""), image: UIImage(named: "playlists"))
+        case .videoPlaylists:
+            return IndicatorInfo(title: NSLocalizedString("playlists", comment: ""), image: UIImage(named: "playlists"))
+        case .allVideos:
+            return IndicatorInfo(title: NSLocalizedString("videos", comment: ""), image: UIImage(named: "videos"))
+        }
+
+    }
+
+
+    @objc func object(at index: Int, subcategory: VLCMediaSubcategory) -> Any {
 
         guard index >= 0 else {
             preconditionFailure("a negative value ? I don't think so!")
@@ -106,8 +127,8 @@ struct VLCMediaType {
         preconditionFailure("index is taller than count")
     }
 
-    func allObjects(for subcategory: VLCMediaSubcategory) -> [MLFile] {
-        return array(for: subcategory)
+    func allObjects(for subcategory: VLCMediaSubcategory) -> [Any] {
+        return array(for:subcategory)
     }
 
     internal func removeObject(at index: Int, subcategory: VLCMediaSubcategory) {
@@ -139,30 +160,88 @@ struct VLCMediaType {
                 ($0 as MLFile).isKind(ofType: kMLFileTypeTVShowEpisode) ||
                 ($0 as MLFile).isKind(ofType: kMLFileTypeClip)
         }
-        allVideosFromVideos()
-        // TODO: generate video subcategories
+        moviesFromVideos()
+        episodesFromVideos()
+        videoPlaylistsFromVideos()
     }
 
     private func getAllAudio() {
         let files = MLFile.allFiles() as! [MLFile]
         foundAudio = files.filter { $0.isSupportedAudioFile() }
-        tracksFromAudio()
-        // TODO: generate remaining subcategories
+
+        artistsFromAudio()
+        albumsFromAudio()
+        audioPlaylistsFromAudio()
+        genresFromAudio()
+    }
+
+    private func artistsFromAudio() {
+        let albumtracks = MLAlbumTrack.allTracks() as! [MLAlbumTrack]
+        let tracksWithArtist = albumtracks.filter {
+            $0.artist != nil && $0.artist != ""
+            }
+        artists = tracksWithArtist.map{ $0.artist }
     }
 
-    private func tracksFromAudio() {
-        if tracks != foundAudio {
-            tracks = foundAudio
-            NotificationCenter.default.post(name: .VLCTracksDidChangeNotification, object: tracks)
+    private func genresFromAudio(){
+        let albumtracks = MLAlbumTrack.allTracks() as! [MLAlbumTrack]
+        let tracksWithArtist = albumtracks.filter {
+            $0.genre != nil && $0.genre != ""
         }
+        genres = tracksWithArtist.map{ $0.genre }
     }
 
-    private func allVideosFromVideos() {
-        if allVideos != foundVideos {
-            allVideos = foundVideos
-            NotificationCenter.default.post(name: .VLCAllVideosDidChangeNotification, object: allVideos)
+    private func episodesFromVideos() {
+        episodes = MLShowEpisode.allEpisodes() as! [MLShowEpisode]
+
+    }
+
+    private func albumsFromAudio(){
+        albums = MLAlbum.allAlbums() as! [MLAlbum]
+    }
+
+    private func audioPlaylistsFromAudio() {
+        let labels = MLLabel.allLabels() as! [MLLabel]
+        audioPlaylist = labels.filter {
+            let audioFiles = $0.files.filter{
+                if let file = $0 as? MLFile {
+                    return file.isSupportedAudioFile()
+                }
+                return false
+            }
+            return !audioFiles.isEmpty
+        }
+    }
+    private func videoPlaylistsFromVideos() {
+        let labels = MLLabel.allLabels() as! [MLLabel]
+        audioPlaylist = labels.filter {
+            let audioFiles = $0.files.filter{
+                if let file = $0 as? MLFile {
+                    return file.isShowEpisode() || file.isMovie() || file.isClip()
+                }
+                return false
+            }
+            return !audioFiles.isEmpty
         }
     }
+
+    private func moviesFromVideos() {
+        movies = foundVideos.filter{ $0.isMovie() }
+    }
+//    private func tracksFromAudio() {
+//        if tracks != foundAudio {
+//            tracks = foundAudio
+//            NotificationCenter.default.post(name: .VLCTracksDidChangeNotification, object: tracks)
+//        }
+//    }
+//
+//    private func allVideosFromVideos() {
+//        if allVideos != foundVideos {
+//            allVideos = foundVideos
+//            NotificationCenter.default.post(name: .VLCAllVideosDidChangeNotification, object: allVideos)
+//        }
+//    }
+
 }
 
 // Todo: implement the remove

+ 4 - 6
Sources/VLCTabBarCoordinator.swift

@@ -55,9 +55,8 @@ class VLCTabbarCooordinator: NSObject, VLCMediaViewControllerDelegate {
         displayController.view.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: tabBarController.tabBar.frame.size.height, right: 0)
         displayController.didMove(toParentViewController: tabBarController)
 
-        let videoVC = VLCMediaViewController(services: services, type: VLCMediaType(category: .video, subcategory: .allVideos))
-        //this should probably not be the delegate
-        videoVC.delegate = self
+        let videoVC = VLCVideoSubcategoryViewController(services: services)
+        //videoVC.delegate = self
         videoVC.title = NSLocalizedString("VIDEO", comment: "")
         videoVC.tabBarItem = UITabBarItem(
             title: NSLocalizedString("VIDEO", comment: ""),
@@ -66,9 +65,8 @@ class VLCTabbarCooordinator: NSObject, VLCMediaViewControllerDelegate {
         videoVC.tabBarItem.accessibilityIdentifier = VLCAccessibilityIdentifier.video
 
         // Audio
-        let audioVC = VLCMediaViewController(services: services, type: VLCMediaType(category: .audio, subcategory: .tracks))
-        //this should probably not be the delegate
-        audioVC.delegate = self
+        let audioVC = VLCAudioSubcategoryViewController(services: services)
+        //audioVC.delegate = self
         audioVC.title = NSLocalizedString("AUDIO", comment: "")
         audioVC.tabBarItem = UITabBarItem(
             title: NSLocalizedString("AUDIO", comment: ""),

+ 17 - 0
VLC.xcodeproj/project.pbxproj

@@ -22,6 +22,8 @@
 		4152F1621FEF19BD00F1908B /* KeychainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4152F1611FEF19BD00F1908B /* KeychainCoordinator.swift */; };
 		416443862048419E00CAC646 /* DeviceMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416443852048419E00CAC646 /* DeviceMotion.swift */; };
 		416DACB720B6DB9A001BC75D /* VLCPlayingExternallyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416DACB620B6DB9A001BC75D /* VLCPlayingExternallyView.swift */; };
+		4170152C209A1D3600802E44 /* MediaSubcategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4170152B209A1D3600802E44 /* MediaSubcategoryViewController.swift */; };
+		41701546209B36E800802E44 /* BaseButtonBarPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41701545209B36E800802E44 /* BaseButtonBarPagerTabStripViewController.swift */; };
 		417CDA231A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 417CDA211A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.m */; };
 		417CDA241A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 417CDA221A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.xib */; };
 		417D7F601F7BA26200DDF36A /* VLCRemoteControlService.m in Sources */ = {isa = PBXBuildFile; fileRef = 417D7F5F1F7BA26200DDF36A /* VLCRemoteControlService.m */; };
@@ -50,6 +52,8 @@
 		41EB91D71F7BE6F500821AA5 /* VLCRemoteControlService.m in Sources */ = {isa = PBXBuildFile; fileRef = 417D7F5F1F7BA26200DDF36A /* VLCRemoteControlService.m */; };
 		41EB91DD1F7BFF8500821AA5 /* VLCMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 41EB91DC1F7BFF8400821AA5 /* VLCMetadata.m */; };
 		41EB91DE1F7BFF8500821AA5 /* VLCMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 41EB91DC1F7BFF8400821AA5 /* VLCMetadata.m */; };
+		41EB94B4209B46E9002F4C30 /* ButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41EB94B3209B46E9002F4C30 /* ButtonCell.xib */; };
+		41EB94B6209B4869002F4C30 /* IconLabelCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41EB94B5209B4869002F4C30 /* IconLabelCell.xib */; };
 		41F5C0781F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 41F5C0771F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.m */; };
 		41F9BC7C1F4F20E400268461 /* VLCTrackSelectorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 41F9BC7B1F4F20E400268461 /* VLCTrackSelectorView.m */; };
 		41FCD2F820B565B600660AAB /* VLCAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41FCD2F720B565B500660AAB /* VLCAlertViewController.swift */; };
@@ -498,6 +502,8 @@
 		416443852048419E00CAC646 /* DeviceMotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeviceMotion.swift; path = Sources/DeviceMotion.swift; sourceTree = "<group>"; };
 		416DACB620B6DB9A001BC75D /* VLCPlayingExternallyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayingExternallyView.swift; sourceTree = "<group>"; };
 		416DEFF51FEEA76A00F4FC59 /* LayoutAnchorContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LayoutAnchorContainer.swift; path = Sources/LayoutAnchorContainer.swift; sourceTree = "<group>"; };
+		4170152B209A1D3600802E44 /* MediaSubcategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaSubcategoryViewController.swift; path = Sources/MediaSubcategoryViewController.swift; sourceTree = "<group>"; };
+		41701545209B36E800802E44 /* BaseButtonBarPagerTabStripViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BaseButtonBarPagerTabStripViewController.swift; path = Sources/BaseButtonBarPagerTabStripViewController.swift; sourceTree = "<group>"; };
 		417CDA201A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCCloudServicesTableViewController.h; path = Sources/VLCCloudServicesTableViewController.h; sourceTree = SOURCE_ROOT; };
 		417CDA211A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCCloudServicesTableViewController.m; path = Sources/VLCCloudServicesTableViewController.m; sourceTree = SOURCE_ROOT; };
 		417CDA221A48D1F300D9ACE7 /* VLCCloudServicesTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = VLCCloudServicesTableViewController.xib; path = Resources/VLCCloudServicesTableViewController.xib; sourceTree = SOURCE_ROOT; };
@@ -577,6 +583,8 @@
 		41E6BECC207E64E900E158BA /* RemoteNetworkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteNetworkCell.swift; sourceTree = "<group>"; };
 		41EB91DB1F7BFF8400821AA5 /* VLCMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCMetadata.h; sourceTree = "<group>"; };
 		41EB91DC1F7BFF8400821AA5 /* VLCMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCMetadata.m; sourceTree = "<group>"; };
+		41EB94B3209B46E9002F4C30 /* ButtonCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ButtonCell.xib; sourceTree = "<group>"; };
+		41EB94B5209B4869002F4C30 /* IconLabelCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IconLabelCell.xib; sourceTree = "<group>"; };
 		41F5C0761F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VLCLibrarySearchDisplayDataSource.h; path = Sources/VLCLibrarySearchDisplayDataSource.h; sourceTree = SOURCE_ROOT; };
 		41F5C0771F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = VLCLibrarySearchDisplayDataSource.m; path = Sources/VLCLibrarySearchDisplayDataSource.m; sourceTree = SOURCE_ROOT; };
 		41F9BC7A1F4F20E400268461 /* VLCTrackSelectorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VLCTrackSelectorView.h; path = Sources/VLCTrackSelectorView.h; sourceTree = SOURCE_ROOT; };
@@ -1540,6 +1548,11 @@
 				8F91EC78195CEC7900F5BCBA /* VLCOpenInActivity.m */,
 				41F5C0761F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.h */,
 				41F5C0771F41ED55005EB9CB /* VLCLibrarySearchDisplayDataSource.m */,
+				412831632093323700FAA307 /* VLCMediaDataSource.swift */,
+				4170152B209A1D3600802E44 /* MediaSubcategoryViewController.swift */,
+				41701545209B36E800802E44 /* BaseButtonBarPagerTabStripViewController.swift */,
+				41EB94B3209B46E9002F4C30 /* ButtonCell.xib */,
+				41EB94B5209B4869002F4C30 /* IconLabelCell.xib */,
 			);
 			name = "Everything Playlist";
 			sourceTree = "<group>";
@@ -2752,6 +2765,7 @@
 				419D7F051F54176900AF69A2 /* VLCTimeNavigationTitleView.xib in Resources */,
 				7D1516431868D7E0004B18F3 /* VLCFirstStepsFirstPageViewController~iphone.xib in Resources */,
 				7DF04F4E1961F2B8004A5429 /* web-download.png in Resources */,
+				41EB94B4209B46E9002F4C30 /* ButtonCell.xib in Resources */,
 				41B93C081A53853B00102E8B /* VLCCloudServiceCell.xib in Resources */,
 				29125E5617492219003F03E5 /* index.html in Resources */,
 				7DB638AB185BC0890003887C /* Images.xcassets in Resources */,
@@ -2764,6 +2778,7 @@
 				7D32B384185E293D006CA474 /* Raleway.woff in Resources */,
 				7D9870691A3E03D5009CF27D /* papasscode_marker@2x.png in Resources */,
 				7D5DD5C717590ABF001421E3 /* About Contents.html in Resources */,
+				41EB94B6209B4869002F4C30 /* IconLabelCell.xib in Resources */,
 				7DBBF19B183AB4300009A339 /* VLCCloudStorageTableViewCell~iphone.xib in Resources */,
 				7D92897B1877467E009108FD /* VLCFirstStepsFourthPageViewController~iphone.xib in Resources */,
 				7D1516461868D7E0004B18F3 /* VLCFirstStepsSixthPageViewController~iphone.xib in Resources */,
@@ -3197,6 +3212,7 @@
 				8D437154205808FF00F36458 /* VLCActionSheet.swift in Sources */,
 				DDF908E41CFCD97400108B70 /* VLCNetworkLoginDataSourceProtocol.m in Sources */,
 				7D378492183A98BF009EE944 /* VLCExternalDisplayController.m in Sources */,
+				4170152C209A1D3600802E44 /* MediaSubcategoryViewController.swift in Sources */,
 				41F9BC7C1F4F20E400268461 /* VLCTrackSelectorView.m in Sources */,
 				7D378499183A98D1009EE944 /* VLCPlaylistCollectionViewCell.m in Sources */,
 				DD8F84311B00EB3B0009138A /* VLCPlaybackController+MediaLibrary.m in Sources */,
@@ -3256,6 +3272,7 @@
 				7D5CAA8C1A4AD8E5003F2CBC /* VLCTrackSelectorHeaderView.m in Sources */,
 				DD870E951CEF78D800BBD4FE /* VLCNetworkLoginDataSourceLogin.m in Sources */,
 				CAC0AFED20CF8F6F00EDB035 /* VLCAccessibilityIdentifier.swift in Sources */,
+				41701546209B36E800802E44 /* BaseButtonBarPagerTabStripViewController.swift in Sources */,
 				7D398DC11CC3E709002C999A /* VLCLocalNetworkServiceBrowserBonjour.m in Sources */,
 				7D30F3CA183AB27A00FFC021 /* VLCDownloadViewController.m in Sources */,
 				DDEAECC61BDEC79D00756C83 /* VLCLocalNetworkServiceBrowserSAP.m in Sources */,