Add stretchy menus
This commit is contained in:
parent
8e6bf219c8
commit
503d35f301
|
@ -48,6 +48,7 @@
|
|||
D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = D6E24370278BE1250005E546 /* HTMLEntities */; };
|
||||
D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; };
|
||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */; };
|
||||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -131,6 +132,7 @@
|
|||
D6E2436D278BD8160005E546 /* ReadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadViewController.swift; sourceTree = "<group>"; };
|
||||
D6E24372278BE2B80005E546 /* read.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = read.css; sourceTree = "<group>"; };
|
||||
D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = "<group>"; };
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StretchyMenuInteraction.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -242,6 +244,7 @@
|
|||
D65B18B527504920004A9448 /* FervorController.swift */,
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */,
|
||||
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
||||
D6A8A33527766E9300CCEC72 /* CoreData */,
|
||||
D65B18AF2750468B004A9448 /* Screens */,
|
||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||
|
@ -499,6 +502,7 @@
|
|||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */,
|
||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */,
|
||||
D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */,
|
||||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */,
|
||||
D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */,
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */,
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||
|
|
|
@ -11,6 +11,8 @@ class AppNavigationController: UINavigationController, UINavigationControllerDel
|
|||
|
||||
private var statusBarBlockingView: UIView!
|
||||
|
||||
static let panRecognizerName = "AppNavPanRecognizer"
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -21,6 +23,7 @@ class AppNavigationController: UINavigationController, UINavigationControllerDel
|
|||
interactivePopGestureRecognizer?.isEnabled = false
|
||||
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized))
|
||||
recognizer.allowedScrollTypesMask = .continuous
|
||||
recognizer.name = AppNavigationController.panRecognizerName
|
||||
view.addGestureRecognizer(recognizer)
|
||||
|
||||
isNavigationBarHidden = true
|
||||
|
|
|
@ -33,6 +33,10 @@ class HomeViewController: UIViewController {
|
|||
// todo: account info
|
||||
title = "Reader"
|
||||
|
||||
view.addInteraction(StretchyMenuInteraction(delegate: self))
|
||||
|
||||
view.backgroundColor = .appBackground
|
||||
|
||||
var config = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
config.headerMode = .supplementary
|
||||
config.backgroundColor = .appBackground
|
||||
|
@ -236,3 +240,19 @@ extension HomeViewController: UICollectionViewDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HomeViewController: StretchyMenuInteractionDelegate {
|
||||
func stretchyMenuTitle() -> String? {
|
||||
return "Switch Accounts"
|
||||
}
|
||||
func stretchyMenuItems() -> [StretchyMenuItem] {
|
||||
return [
|
||||
StretchyMenuItem(title: "foo", subtitle: "bar", action: {
|
||||
print("foo")
|
||||
}),
|
||||
StretchyMenuItem(title: "baz", subtitle: "qux", action: {
|
||||
print("baz")
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ class ReadViewController: UIViewController {
|
|||
navigationItem.largeTitleDisplayMode = .never
|
||||
|
||||
view.backgroundColor = .appBackground
|
||||
view.addInteraction(StretchyMenuInteraction(delegate: self))
|
||||
|
||||
let webView = WKWebView()
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -167,3 +168,26 @@ extension ReadViewController: WKUIDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ReadViewController: StretchyMenuInteractionDelegate {
|
||||
func stretchyMenuTitle() -> String? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func stretchyMenuItems() -> [StretchyMenuItem] {
|
||||
guard let url = item.url else {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
StretchyMenuItem(title: "Open in Safari", subtitle: nil, action: { [unowned self] in
|
||||
self.present(createSafariVC(url: url), animated: true)
|
||||
}),
|
||||
StretchyMenuItem(title: "Share", subtitle: nil, action: { [unowned self] in
|
||||
self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true)
|
||||
}),
|
||||
StretchyMenuItem(title: item.read ? "Mark as Unread" : "Mark as Read", subtitle: nil, action: { [unowned self] in
|
||||
item.read = !item.read
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,405 @@
|
|||
//
|
||||
// StretchyMenuInteraction.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/11/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
struct StretchyMenuItem {
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
let action: () -> Void
|
||||
}
|
||||
|
||||
protocol StretchyMenuInteractionDelegate: AnyObject {
|
||||
func stretchyMenuTitle() -> String?
|
||||
func stretchyMenuItems() -> [StretchyMenuItem]
|
||||
}
|
||||
|
||||
class StretchyMenuInteraction: NSObject, UIInteraction {
|
||||
|
||||
weak var delegate: StretchyMenuInteractionDelegate?
|
||||
|
||||
private(set) weak var view: UIView? = nil
|
||||
|
||||
private let menuHintView = MenuHintView()
|
||||
fileprivate let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||
private var snapshot: UIView?
|
||||
private var menuView: MenuView?
|
||||
|
||||
private let menuOpenThreshold: CGFloat = 100
|
||||
|
||||
fileprivate var isShowingMenu = false
|
||||
|
||||
init(delegate: StretchyMenuInteractionDelegate) {
|
||||
self.delegate = delegate
|
||||
|
||||
menuHintView.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
func willMove(to view: UIView?) {
|
||||
if self.view != nil {
|
||||
fatalError("removing StretchyMenuInteraction from view is unsupported")
|
||||
}
|
||||
}
|
||||
|
||||
func didMove(to view: UIView?) {
|
||||
self.view = view
|
||||
|
||||
guard let view = view else {
|
||||
return
|
||||
}
|
||||
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized))
|
||||
panRecognizer.delegate = self
|
||||
view.addGestureRecognizer(panRecognizer)
|
||||
|
||||
}
|
||||
|
||||
private var prevTranslation: CGFloat = 0
|
||||
|
||||
@objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
|
||||
guard let view = view,
|
||||
!isShowingMenu,
|
||||
let delegate = delegate else {
|
||||
return
|
||||
}
|
||||
|
||||
let prevTranslation = self.prevTranslation
|
||||
let translation = recognizer.translation(in: view)
|
||||
self.prevTranslation = translation.x
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
snapshot = view.snapshotView(afterScreenUpdates: false)
|
||||
let effectiveTranslation = 0.5 * max(0, -translation.x)
|
||||
snapshot!.transform = CGAffineTransform(translationX: -effectiveTranslation, y: 0)
|
||||
view.addSubview(snapshot!)
|
||||
view.insertSubview(menuHintView, belowSubview: snapshot!)
|
||||
menuHintView.backgroundColor = view.backgroundColor
|
||||
menuHintView.frame = CGRect(x: view.bounds.width - effectiveTranslation, y: 0, width: effectiveTranslation, height: view.bounds.height)
|
||||
menuHintView.updateForProgress(0, animate: false)
|
||||
feedbackGenerator.prepare()
|
||||
|
||||
case .changed:
|
||||
let effectiveTranslation = 0.5 * max(0, -translation.x)
|
||||
snapshot!.transform = CGAffineTransform(translationX: -effectiveTranslation, y: 0)
|
||||
if -prevTranslation < menuOpenThreshold && -translation.x >= menuOpenThreshold {
|
||||
feedbackGenerator.impactOccurred()
|
||||
}
|
||||
menuHintView.frame = CGRect(x: view.bounds.width - effectiveTranslation, y: 0, width: effectiveTranslation, height: view.bounds.height)
|
||||
menuHintView.updateForProgress(-translation.x / menuOpenThreshold, animate: true)
|
||||
|
||||
case .ended:
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) {
|
||||
self.snapshot!.transform = .identity
|
||||
} completion: { _ in
|
||||
self.snapshot!.removeFromSuperview()
|
||||
self.menuHintView.removeFromSuperview()
|
||||
}
|
||||
|
||||
|
||||
if -translation.x > menuOpenThreshold {
|
||||
guard let rootView = view.window?.rootViewController?.view else {
|
||||
return
|
||||
}
|
||||
let menuView = MenuView(title: delegate.stretchyMenuTitle(), items: delegate.stretchyMenuItems(), owner: self)
|
||||
self.menuView = menuView
|
||||
menuView.translatesAutoresizingMaskIntoConstraints = false
|
||||
menuView.layer.zPosition = 102
|
||||
rootView.addSubview(menuView)
|
||||
NSLayoutConstraint.activate([
|
||||
menuView.leadingAnchor.constraint(equalTo: rootView.leadingAnchor),
|
||||
menuView.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
|
||||
menuView.topAnchor.constraint(equalTo: rootView.topAnchor),
|
||||
menuView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor),
|
||||
])
|
||||
rootView.layoutIfNeeded()
|
||||
menuView.animateIn()
|
||||
|
||||
isShowingMenu = true
|
||||
feedbackGenerator.prepare()
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func hideMenu(completion: (() -> Void)? = nil) {
|
||||
guard let menuView = menuView else {
|
||||
return
|
||||
}
|
||||
|
||||
menuView.animateOut() {
|
||||
menuView.removeFromSuperview()
|
||||
self.isShowingMenu = false
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension StretchyMenuInteraction: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return otherGestureRecognizer.name == AppNavigationController.panRecognizerName
|
||||
}
|
||||
}
|
||||
|
||||
private class MenuHintView: UIView {
|
||||
private let pill = UIView()
|
||||
private var progress: CGFloat = 0
|
||||
|
||||
private var animator: UIViewPropertyAnimator?
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
pill.frame = CGRect(x: 0, y: 0, width: 5, height: 50)
|
||||
pill.backgroundColor = .systemGray
|
||||
pill.layer.cornerRadius = 2.5
|
||||
addSubview(pill)
|
||||
|
||||
pill.backgroundColor = .systemGray
|
||||
pill.frame.size.height = 50
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let width: CGFloat = 5
|
||||
pill.frame.origin.x = bounds.midX - width / 2
|
||||
pill.frame.origin.y = bounds.midY - pill.frame.height / 2
|
||||
}
|
||||
|
||||
func updateForProgress(_ progress: CGFloat, animate: Bool) {
|
||||
let oldCompleted = self.progress >= 1
|
||||
let completed = progress >= 1
|
||||
|
||||
self.progress = progress
|
||||
|
||||
if oldCompleted != completed {
|
||||
func updatePill() {
|
||||
pill.backgroundColor = completed ? .tintColor : .systemGray
|
||||
let height: CGFloat = completed ? 75 : 50
|
||||
pill.frame.origin.y = bounds.midY - height / 2
|
||||
pill.frame.size.height = height
|
||||
}
|
||||
|
||||
animator?.stopAnimation(true)
|
||||
if animate {
|
||||
animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.5, animations: updatePill)
|
||||
animator!.startAnimation()
|
||||
} else {
|
||||
updatePill()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MenuView: UIView {
|
||||
|
||||
weak var owner: StretchyMenuInteraction?
|
||||
|
||||
private let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialDark)
|
||||
private var blurView: UIView!
|
||||
private let optionsStack = UIStackView()
|
||||
|
||||
private let items: [StretchyMenuItem]
|
||||
|
||||
init(title: String?, items: [StretchyMenuItem], owner: StretchyMenuInteraction) {
|
||||
self.items = items
|
||||
self.owner = owner
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
blurView = UIVisualEffectView(effect: blurEffect)
|
||||
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(blurView)
|
||||
|
||||
optionsStack.axis = .vertical
|
||||
optionsStack.alignment = .fill
|
||||
optionsStack.spacing = 2
|
||||
optionsStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
for item in items {
|
||||
optionsStack.addArrangedSubview(MenuItemView(item: item, owner: owner))
|
||||
}
|
||||
addSubview(optionsStack)
|
||||
|
||||
if let title = title {
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.text = title
|
||||
titleLabel.textAlignment = .right
|
||||
titleLabel.font = .preferredFont(forTextStyle: .title1)
|
||||
let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect, style: .label))
|
||||
vibrancyView.contentView.addSubview(titleLabel)
|
||||
optionsStack.insertArrangedSubview(vibrancyView, at: 0)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: vibrancyView.contentView.leadingAnchor, multiplier: 1),
|
||||
vibrancyView.contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: titleLabel.trailingAnchor, multiplier: 1),
|
||||
titleLabel.topAnchor.constraint(equalTo: vibrancyView.contentView.topAnchor),
|
||||
titleLabel.bottomAnchor.constraint(equalTo: vibrancyView.contentView.bottomAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
blurView.topAnchor.constraint(equalTo: topAnchor),
|
||||
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
|
||||
optionsStack.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
optionsStack.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
optionsStack.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
])
|
||||
|
||||
blurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(blurTapped)))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
blurView.layer.opacity = 0
|
||||
let count = optionsStack.arrangedSubviews.count
|
||||
for (index, item) in optionsStack.arrangedSubviews.enumerated() {
|
||||
let multiplier = (1 + CGFloat(index) * 1 / CGFloat(count - 1))
|
||||
item.transform = CGAffineTransform(translationX: bounds.width * multiplier, y: 0)
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.35, delay: 0, options: .curveEaseInOut) {
|
||||
self.blurView.layer.opacity = 1
|
||||
for item in self.optionsStack.arrangedSubviews {
|
||||
item.transform = .identity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
UIView.animate(withDuration: 0.35, delay: 0, options: .curveEaseInOut) {
|
||||
self.blurView.layer.opacity = 0
|
||||
let count = self.optionsStack.arrangedSubviews.count
|
||||
for (index, item) in self.optionsStack.arrangedSubviews.enumerated() {
|
||||
let multiplier = (1 + CGFloat(index) * 1 / CGFloat(count - 1))
|
||||
item.transform = CGAffineTransform(translationX: self.bounds.width * -multiplier, y: 0)
|
||||
}
|
||||
} completion: { _ in
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func blurTapped() {
|
||||
owner?.hideMenu()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MenuItemView: UIView {
|
||||
weak var owner: StretchyMenuInteraction?
|
||||
|
||||
private let item: StretchyMenuItem
|
||||
|
||||
init(item: StretchyMenuItem, owner: StretchyMenuInteraction) {
|
||||
self.item = item
|
||||
self.owner = owner
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
backgroundColor = .appBackground
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.text = item.title
|
||||
titleLabel.textColor = .tintColor
|
||||
titleLabel.textAlignment = .right
|
||||
titleLabel.font = .preferredFont(forTextStyle: .title2)
|
||||
addSubview(titleLabel)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
||||
titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1),
|
||||
trailingAnchor.constraint(equalToSystemSpacingAfter: titleLabel.trailingAnchor, multiplier: 1),
|
||||
|
||||
heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
|
||||
])
|
||||
|
||||
if let subtitle = item.subtitle {
|
||||
let subtitleLabel = UILabel()
|
||||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
subtitleLabel.text = subtitle
|
||||
subtitleLabel.textColor = .secondaryLabel
|
||||
subtitleLabel.textAlignment = .right
|
||||
addSubview(subtitleLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
subtitleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1),
|
||||
trailingAnchor.constraint(equalToSystemSpacingAfter: subtitleLabel.trailingAnchor, multiplier: 1),
|
||||
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
|
||||
bottomAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 4),
|
||||
])
|
||||
} else {
|
||||
NSLayoutConstraint.activate([
|
||||
bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
|
||||
])
|
||||
}
|
||||
|
||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(itemTapped)))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func itemTapped() {
|
||||
owner?.feedbackGenerator.impactOccurred()
|
||||
|
||||
UIView.animateKeyframes(withDuration: 0.15, delay: 0, options: []) {
|
||||
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
||||
self.backgroundColor = .appSecondaryBackground
|
||||
}
|
||||
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
|
||||
self.backgroundColor = .appBackground
|
||||
}
|
||||
} completion: { _ in
|
||||
self.owner?.hideMenu() {
|
||||
self.item.action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addBorders(top: Bool, bottom: Bool) {
|
||||
if top {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .systemGray
|
||||
addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
view.heightAnchor.constraint(equalToConstant: 1),
|
||||
view.topAnchor.constraint(equalTo: topAnchor),
|
||||
view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
if bottom {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .systemGray
|
||||
addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
view.heightAnchor.constraint(equalToConstant: 1),
|
||||
view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,17 @@ extension UIColor {
|
|||
}
|
||||
}
|
||||
|
||||
static let appSecondaryBackground = UIColor { traitCollection in
|
||||
switch traitCollection.userInterfaceStyle {
|
||||
case .dark:
|
||||
return UIColor(white: 0.2, alpha: 1)
|
||||
case .unspecified, .light:
|
||||
fallthrough
|
||||
@unknown default:
|
||||
return UIColor(white: 0.8, alpha: 1)
|
||||
}
|
||||
}
|
||||
|
||||
static let appCellHighlightBackground = UIColor { traitCollection in
|
||||
switch traitCollection.userInterfaceStyle {
|
||||
case .dark:
|
||||
|
|
Loading…
Reference in New Issue