Commit cb77dc7e authored by Soomin Lee's avatar Soomin Lee

Chromecast: Initial integration

parent 6c02f625
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Sdb-a5-RTT"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Sdb-a5-RTT">
<rect key="frame" x="662" y="5" width="30" height="30"/> <rect key="frame" x="670" y="5" width="30" height="30"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="Ap6-VL-6UH"/> <constraint firstAttribute="height" constant="30" id="Ap6-VL-6UH"/>
<constraint firstAttribute="width" constant="30" id="dEx-l7-vH3"/> <constraint firstAttribute="width" constant="30" id="dEx-l7-vH3"/>
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x1t-3m-s0G"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x1t-3m-s0G">
<rect key="frame" x="618" y="5" width="44" height="30"/> <rect key="frame" x="626" y="5" width="44" height="30"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="fv5-Cp-MOv"/> <constraint firstAttribute="height" constant="30" id="fv5-Cp-MOv"/>
</constraints> </constraints>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
</connections> </connections>
</button> </button>
<slider opaque="NO" contentMode="scaleToFill" semanticContentAttribute="playback" insetsLayoutMarginsFromSafeArea="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="axb-6M-lSX" userLabel="Position Slider" customClass="VLCOBSlider"> <slider opaque="NO" contentMode="scaleToFill" semanticContentAttribute="playback" insetsLayoutMarginsFromSafeArea="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="axb-6M-lSX" userLabel="Position Slider" customClass="VLCOBSlider">
<rect key="frame" x="28" y="5" width="584" height="31"/> <rect key="frame" x="28" y="5" width="592" height="31"/>
<accessibility key="accessibilityConfiguration"> <accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" updatesFrequently="YES"/> <accessibilityTraits key="traits" updatesFrequently="YES"/>
</accessibility> </accessibility>
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
<constraint firstItem="axb-6M-lSX" firstAttribute="trailing" secondItem="x1t-3m-s0G" secondAttribute="leading" constant="-8" id="l05-q4-Dha"/> <constraint firstItem="axb-6M-lSX" firstAttribute="trailing" secondItem="x1t-3m-s0G" secondAttribute="leading" constant="-8" id="l05-q4-Dha"/>
<constraint firstItem="x1t-3m-s0G" firstAttribute="centerY" secondItem="SbC-6V-aXT" secondAttribute="centerY" id="mIZ-5x-1r6"/> <constraint firstItem="x1t-3m-s0G" firstAttribute="centerY" secondItem="SbC-6V-aXT" secondAttribute="centerY" id="mIZ-5x-1r6"/>
<constraint firstItem="SBq-Am-6MD" firstAttribute="leading" secondItem="SbC-6V-aXT" secondAttribute="leading" id="mf3-NF-cwy"/> <constraint firstItem="SBq-Am-6MD" firstAttribute="leading" secondItem="SbC-6V-aXT" secondAttribute="leading" id="mf3-NF-cwy"/>
<constraint firstAttribute="trailing" secondItem="Sdb-a5-RTT" secondAttribute="trailing" constant="8" id="uI1-3o-gyT"/> <constraint firstAttribute="trailing" secondItem="Sdb-a5-RTT" secondAttribute="trailing" id="uI1-3o-gyT"/>
<constraint firstItem="axb-6M-lSX" firstAttribute="leading" secondItem="SBq-Am-6MD" secondAttribute="trailing" id="zQP-Wv-QPC"/> <constraint firstItem="axb-6M-lSX" firstAttribute="leading" secondItem="SBq-Am-6MD" secondAttribute="trailing" id="zQP-Wv-QPC"/>
</constraints> </constraints>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"ONE_SPU_TRACK" = "One subtitles track"; "ONE_SPU_TRACK" = "One subtitles track";
"PLAYING_EXTERNALLY_TITLE" = "TV Connected"; "PLAYING_EXTERNALLY_TITLE" = "TV Connected";
"PLAYING_EXTERNALLY_TITLE_CHROMECAST" = "Chromecast Connected";
"PLAYING_EXTERNALLY_DESC" = "This video is playing on the TV"; "PLAYING_EXTERNALLY_DESC" = "This video is playing on the TV";
"VFILTER_HUE" = "Hue"; "VFILTER_HUE" = "Hue";
...@@ -70,6 +71,9 @@ ...@@ -70,6 +71,9 @@
"PRIVATE_PLAYBACK_TOGGLE" = "Private Playback"; "PRIVATE_PLAYBACK_TOGGLE" = "Private Playback";
"SCAN_SUBTITLE_TOGGLE" = "Scan for Subtitles (http-only)"; "SCAN_SUBTITLE_TOGGLE" = "Scan for Subtitles (http-only)";
"BUTTON_RENDERER" = "Casting devices";
"BUTTON_RENDERER_HINT" = "Show a menu of available casting devices";
"UPGRADING_LIBRARY" = "Upgrading Media Library"; "UPGRADING_LIBRARY" = "Upgrading Media Library";
"UNTITLED_SHOW" = "Untitled Show"; "UNTITLED_SHOW" = "Untitled Show";
"UNKNOWN" = "Unknown"; "UNKNOWN" = "Unknown";
...@@ -115,6 +119,7 @@ ...@@ -115,6 +119,7 @@
"SECTION_HEADER_LIBRARY" = "Media Library"; "SECTION_HEADER_LIBRARY" = "Media Library";
"STREAMVC_DETAILTEXT" = "Play streams directly without downloading"; "STREAMVC_DETAILTEXT" = "Play streams directly without downloading";
"DOWNLOADVC_DETAILTEXT" = "Download files directly to your device"; "DOWNLOADVC_DETAILTEXT" = "Download files directly to your device";
"HEADER_TITLE_RENDERER" = "Select a casting device";
"LOCAL_NETWORK" = "Local Network"; "LOCAL_NETWORK" = "Local Network";
"CONNECT_TO_SERVER" = "Connect to Server"; "CONNECT_TO_SERVER" = "Connect to Server";
......
...@@ -14,6 +14,7 @@ import Foundation ...@@ -14,6 +14,7 @@ import Foundation
@objc(VLCService) @objc(VLCService)
public class Services: NSObject { public class Services: NSObject {
@objc let mediaDataSource = VLCMediaDataSource() @objc let mediaDataSource = VLCMediaDataSource()
@objc let rendererDiscovererManager = VLCRendererDiscovererManager(presentingViewController: nil)
} }
@objc class AppCoordinator: NSObject { @objc class AppCoordinator: NSObject {
......
...@@ -24,6 +24,8 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults ...@@ -24,6 +24,8 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
private var searchController: UISearchController? private var searchController: UISearchController?
private let searchDataSource = VLCLibrarySearchDisplayDataSource() private let searchDataSource = VLCLibrarySearchDisplayDataSource()
private var mediaType: VLCMediaType private var mediaType: VLCMediaType
private var rendererButton: UIButton
public weak var delegate: VLCMediaViewControllerDelegate? public weak var delegate: VLCMediaViewControllerDelegate?
@available(iOS 11.0, *) @available(iOS 11.0, *)
...@@ -49,6 +51,7 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults ...@@ -49,6 +51,7 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
init(services: Services, type: VLCMediaType) { init(services: Services, type: VLCMediaType) {
self.services = services self.services = services
mediaType = type mediaType = type
self.rendererButton = services.rendererDiscovererManager.setupRendererButton()
super.init(collectionViewLayout: UICollectionViewFlowLayout()) super.init(collectionViewLayout: UICollectionViewFlowLayout())
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .VLCThemeDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .VLCThemeDidChangeNotification, object: nil)
if mediaType.category == .video { if mediaType.category == .video {
...@@ -73,9 +76,20 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults ...@@ -73,9 +76,20 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
setupCollectionView() setupCollectionView()
setupSearchController() setupSearchController()
setupNavigationBar() setupNavigationBar()
setupRendererButton()
_ = (MLMediaLibrary.sharedMediaLibrary() as! MLMediaLibrary).libraryDidAppear() _ = (MLMediaLibrary.sharedMediaLibrary() as! MLMediaLibrary).libraryDidAppear()
} }
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let manager = services.rendererDiscovererManager
if manager.discoverers.isEmpty {
// Either didn't start or stopped before
manager.start()
}
manager.presentingViewController = self
}
@objc func themeDidChange() { @objc func themeDidChange() {
collectionView?.backgroundColor = PresentationTheme.current.colors.background collectionView?.backgroundColor = PresentationTheme.current.colors.background
} }
...@@ -128,6 +142,11 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults ...@@ -128,6 +142,11 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
collectionView?.backgroundView = collectionView?.numberOfItems(inSection: 0) == 0 ? emptyView : nil collectionView?.backgroundView = collectionView?.numberOfItems(inSection: 0) == 0 ? emptyView : nil
} }
// MARK: Renderer
private func setupRendererButton() {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rendererButton)
}
@objc func sort() { @objc func sort() {
delegate?.mediaViewControllerDidSelectSort(self) delegate?.mediaViewControllerDidSelectSort(self)
} }
...@@ -154,5 +173,4 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults ...@@ -154,5 +173,4 @@ public class VLCMediaViewController: UICollectionViewController, UISearchResults
public func didDismissSearchController(_ searchController: UISearchController) { public func didDismissSearchController(_ searchController: UISearchController) {
collectionView?.dataSource = mediaDataSourceAndDelegate collectionView?.dataSource = mediaDataSourceAndDelegate
} }
} }
/*****************************************************************************
* VLCActionSheet.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
import Foundation
import UIKit
@objc protocol VLCActionSheetDataSource {
@objc func numberOfRows() -> Int
@objc func actionSheet(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
}
@objc protocol VLCActionSheetDelegate {
@objc optional func headerViewTitle() -> String?
@objc func itemAtIndexPath(_ indexPath: IndexPath) -> Any?
@objc func actionSheet(collectionView: UICollectionView, didSelectItem item: Any, At indexPath: IndexPath)
}
// MARK: VLCActionSheet
class VLCActionSheet: UIViewController {
private let cellHeight: CGFloat = 50
@objc weak var dataSource: VLCActionSheetDataSource?
@objc weak var delegate: VLCActionSheetDelegate?
var action: ((_ item: Any) -> Void)?
lazy var backgroundView: UIView = {
let backgroundView = UIView()
backgroundView.isHidden = true
backgroundView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.removeActionSheet)))
return backgroundView
}()
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.minimumLineSpacing = 1
collectionViewLayout.minimumInteritemSpacing = 0
return collectionViewLayout
}()
lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = PresentationTheme.current.colors.background
collectionView.alwaysBounceVertical = true
collectionView.showsVerticalScrollIndicator = false
collectionView.register(VLCActionSheetCell.self, forCellWithReuseIdentifier: VLCActionSheetCell.identifier)
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
lazy var headerView: VLCActionSheetSectionHeader = {
let headerView = VLCActionSheetSectionHeader()
headerView.title.text = delegate?.headerViewTitle?() ?? "Default header title"
headerView.title.textColor = PresentationTheme.current.colors.cellTextColor
headerView.backgroundColor = PresentationTheme.current.colors.background
headerView.translatesAutoresizingMaskIntoConstraints = false
return headerView
}()
lazy var bottomBackgroundView: UIView = {
let bottomBackgroundView = UIView()
bottomBackgroundView.backgroundColor = PresentationTheme.current.colors.background
bottomBackgroundView.translatesAutoresizingMaskIntoConstraints = false
return bottomBackgroundView
}()
lazy var mainStackView: UIStackView = {
let mainStackView = UIStackView()
mainStackView.spacing = 0
mainStackView.axis = .vertical
mainStackView.alignment = .center
mainStackView.translatesAutoresizingMaskIntoConstraints = false
return mainStackView
}()
fileprivate lazy var maxCollectionViewHeightConstraint: NSLayoutConstraint = {
let maxCollectionViewHeightConstraint = collectionView.heightAnchor.constraint(
lessThanOrEqualToConstant: (view.bounds.height / 2) - cellHeight)
return maxCollectionViewHeightConstraint
}()
fileprivate lazy var collectionViewHeightConstraint: NSLayoutConstraint = {
guard let dataSource = dataSource else {
preconditionFailure("VLCActionSheet: Data source not set correctly!")
}
let collectionViewHeightConstraint = collectionView.heightAnchor.constraint(
equalToConstant: CGFloat(dataSource.numberOfRows()) * cellHeight)
collectionViewHeightConstraint.priority = .required - 1
return collectionViewHeightConstraint
}()
fileprivate lazy var bottomBackgroundViewHeightConstraint: NSLayoutConstraint = {
let bottomBackgroundViewHeightConstraint = bottomBackgroundView.heightAnchor.constraint(equalToConstant: cellHeight)
return bottomBackgroundViewHeightConstraint
}()
@objc func removeActionSheet() {
UIView.transition(with: backgroundView, duration: 0.01, options: .transitionCrossDissolve, animations: {
[weak self] in
self?.backgroundView.isHidden = true
}, completion: { [weak self] finished in
self?.presentingViewController?.dismiss(animated: true, completion: nil)
})
}
// MARK: Private methods
fileprivate func setuplHeaderViewConstraints() {
NSLayoutConstraint.activate([
headerView.heightAnchor.constraint(equalToConstant: cellHeight),
headerView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
}
fileprivate func setupCollectionViewConstraints() {
NSLayoutConstraint.activate([
collectionViewHeightConstraint,
maxCollectionViewHeightConstraint,
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
}
fileprivate func setupBottomBackgroundView() {
NSLayoutConstraint.activate([
bottomBackgroundViewHeightConstraint,
bottomBackgroundView.widthAnchor.constraint(equalTo: view.widthAnchor)
])
}
fileprivate func setupMainStackViewConstraints() {
NSLayoutConstraint.activate([
mainStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mainStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mainStackView.widthAnchor.constraint(equalTo: view.widthAnchor),
mainStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
override func updateViewConstraints() {
super.updateViewConstraints()
if let presentingViewController = presentingViewController, let dataSource = dataSource {
collectionViewHeightConstraint.constant = CGFloat(dataSource.numberOfRows()) * cellHeight
maxCollectionViewHeightConstraint.constant = presentingViewController.view.frame.size.height / 2
collectionView.setNeedsLayout()
collectionView.layoutIfNeeded()
}
}
@available(iOS 11.0, *)
override func viewSafeAreaInsetsDidChange() {
bottomBackgroundViewHeightConstraint.constant = view.safeAreaInsets.bottom
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(backgroundView)
view.addSubview(mainStackView)
mainStackView.addArrangedSubview(headerView)
mainStackView.addArrangedSubview(collectionView)
mainStackView.addArrangedSubview(bottomBackgroundView)
backgroundView.frame = UIScreen.main.bounds
setupMainStackViewConstraints()
setupCollectionViewConstraints()
setuplHeaderViewConstraints()
setupBottomBackgroundView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
mainStackView.isHidden = true
collectionView.reloadData()
updateViewConstraints()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// This is to avoid a horrible visual glitch!
mainStackView.isHidden = false
UIView.transition(with: backgroundView, duration: 0.2, options: .transitionCrossDissolve, animations: { [weak self] in
self?.backgroundView.isHidden = false
}, completion: nil)
let realMainStackView = mainStackView.frame
mainStackView.frame.origin.y += mainStackView.frame.origin.y
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
[mainStackView] in
mainStackView.frame = realMainStackView
}, completion: nil)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.maxCollectionViewHeightConstraint.constant = size.height / 2
self?.collectionView.layoutIfNeeded()
})
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
@objc func setAction(closure action: @escaping (_ item: Any) -> Void) {
self.action = action
}
}
// MARK: UICollectionViewDelegateFlowLayout
extension VLCActionSheet: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
}
// MARK: UICollectionViewDelegate
extension VLCActionSheet: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = delegate, let item = delegate.itemAtIndexPath(indexPath) {
delegate.actionSheet(collectionView: collectionView, didSelectItem: item, At: indexPath)
action?(item)
}
removeActionSheet()
}
}
// MARK: UICollectionViewDataSource
extension VLCActionSheet: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let dataSource = dataSource {
return dataSource.numberOfRows()
}
preconditionFailure("VLCActionSheet: No data source")
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let dataSource = dataSource {
return dataSource.actionSheet(collectionView: collectionView, cellForItemAt: indexPath)
}
preconditionFailure("VLCActionSheet: No data source")
}
}
/*****************************************************************************
* VLCActionSheetCell.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class VLCActionSheetCell: UICollectionViewCell {
static let identifier = "VLCActionSheetCell"
let icon: UIImageView = {
let icon = UIImageView()
icon.translatesAutoresizingMaskIntoConstraints = false
icon.contentMode = .scaleAspectFit
return icon
}()
let name: UILabel = {
let name = UILabel()
name.textColor = PresentationTheme.current.colors.cellTextColor
name.font = UIFont.systemFont(ofSize: 15)
name.translatesAutoresizingMaskIntoConstraints = false
return name
}()
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.spacing = 15.0
stackView.axis = .horizontal
stackView.alignment = .center
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
private func setupViews() {
backgroundColor = PresentationTheme.current.colors.background
stackView.addArrangedSubview(icon)
stackView.addArrangedSubview(name)
addSubview(stackView)
var guide: LayoutAnchorContainer = self
if #available(iOS 11.0, *) {
guide = safeAreaLayoutGuide
}
NSLayoutConstraint.activate([
icon.heightAnchor.constraint(equalToConstant: 25),
icon.widthAnchor.constraint(equalTo: icon.heightAnchor),
name.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
name.centerYAnchor.constraint(equalTo: stackView.centerYAnchor),
stackView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -20),
stackView.heightAnchor.constraint(equalTo: heightAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor)
])
}
}
/*****************************************************************************
* VLCActionSheetSectionHeader.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class VLCActionSheetSectionHeader: UIView {
static let identifier = "VLCActionSheetSectionHeader"
let title: UILabel = {
let title = UILabel()
title.font = UIFont.systemFont(ofSize: 13)
title.translatesAutoresizingMaskIntoConstraints = false
return title
}()
let separator: UIView = {
let separator = UIView()
separator.backgroundColor = .lightGray
separator.translatesAutoresizingMaskIntoConstraints = false
return separator
}()
lazy var guide: LayoutAnchorContainer = {
var guide: LayoutAnchorContainer = self
if #available(iOS 11.0, *) {
guide = safeAreaLayoutGuide
}
return guide
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupTitle()
setupSeparator()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupTitle()
setupSeparator()
}
fileprivate func setupSeparator() {
addSubview(separator)
NSLayoutConstraint.activate([
separator.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 20),
separator.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -20),
separator.heightAnchor.constraint(equalToConstant: 0.5),
separator.topAnchor.constraint(equalTo: bottomAnchor, constant: -1)
])
}