mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
590 lines
25 KiB
Swift
590 lines
25 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import ComponentFlow
|
|
import SwiftSignalKit
|
|
import ViewControllerComponent
|
|
import ComponentDisplayAdapters
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import TelegramCore
|
|
import MultilineTextComponent
|
|
import MediaEditor
|
|
import MediaScrubberComponent
|
|
import ButtonComponent
|
|
|
|
private final class MediaCoverScreenComponent: Component {
|
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
|
|
|
let context: AccountContext
|
|
let mediaEditor: MediaEditor
|
|
|
|
init(
|
|
context: AccountContext,
|
|
mediaEditor: MediaEditor
|
|
) {
|
|
self.context = context
|
|
self.mediaEditor = mediaEditor
|
|
}
|
|
|
|
static func ==(lhs: MediaCoverScreenComponent, rhs: MediaCoverScreenComponent) -> Bool {
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
final class State: ComponentState {
|
|
enum ImageKey: Hashable {
|
|
case done
|
|
}
|
|
private var cachedImages: [ImageKey: UIImage] = [:]
|
|
func image(_ key: ImageKey) -> UIImage {
|
|
if let image = self.cachedImages[key] {
|
|
return image
|
|
} else {
|
|
var image: UIImage
|
|
switch key {
|
|
case .done:
|
|
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)!
|
|
}
|
|
cachedImages[key] = image
|
|
return image
|
|
}
|
|
}
|
|
|
|
var playerStateDisposable: Disposable?
|
|
var playerState: MediaEditorPlayerState?
|
|
|
|
init(mediaEditor: MediaEditor) {
|
|
super.init()
|
|
|
|
self.playerStateDisposable = (mediaEditor.playerState(framesCount: 16)
|
|
|> deliverOnMainQueue).start(next: { [weak self] playerState in
|
|
if let self {
|
|
if self.playerState != playerState {
|
|
self.playerState = playerState
|
|
self.updated()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
deinit {
|
|
self.playerStateDisposable?.dispose()
|
|
}
|
|
}
|
|
|
|
func makeState() -> State {
|
|
return State(mediaEditor: self.mediaEditor)
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let buttonsContainerView = UIView()
|
|
private let buttonsBackgroundView = UIImageView()
|
|
private let previewContainerView = UIView()
|
|
private let cancelButton = ComponentView<Empty>()
|
|
private let label = ComponentView<Empty>()
|
|
private let doneButton = ComponentView<Empty>()
|
|
private let scrubber = ComponentView<Empty>()
|
|
|
|
private let fadeView = UIView()
|
|
|
|
private var component: MediaCoverScreenComponent?
|
|
private weak var state: State?
|
|
private var environment: ViewControllerComponentContainer.Environment?
|
|
|
|
override init(frame: CGRect) {
|
|
self.buttonsContainerView.clipsToBounds = true
|
|
|
|
self.fadeView.alpha = 0.0
|
|
self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.7)
|
|
|
|
self.buttonsBackgroundView.image = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
context.fill(CGRect(origin: .zero, size: size))
|
|
|
|
context.setBlendMode(.clear)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: -11.0, width: size.width, height: 22.0), cornerWidth: 11.0, cornerHeight: 11.0, transform: nil))
|
|
context.fillPath()
|
|
})?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11)
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.backgroundColor = .clear
|
|
|
|
self.addSubview(self.buttonsContainerView)
|
|
self.buttonsContainerView.addSubview(self.buttonsBackgroundView)
|
|
|
|
self.addSubview(self.fadeView)
|
|
self.addSubview(self.previewContainerView)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func animateInFromEditor() {
|
|
self.buttonsBackgroundView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.2, additive: true)
|
|
|
|
self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
|
|
if let view = self.doneButton.view {
|
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
|
}
|
|
}
|
|
|
|
private var animatingOut = false
|
|
func animateOutToEditor(completion: @escaping () -> Void) {
|
|
self.animatingOut = true
|
|
|
|
self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
|
self.buttonsBackgroundView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.2, removeOnCompletion: false, additive: true)
|
|
|
|
self.label.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
|
|
|
if let view = self.scrubber.view {
|
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
|
completion()
|
|
})
|
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
|
}
|
|
|
|
if let view = self.cancelButton.view {
|
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
|
}
|
|
|
|
if let view = self.doneButton.view {
|
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
|
}
|
|
|
|
self.state?.updated()
|
|
}
|
|
|
|
// override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
// let result = super.hitTest(point, with: event)
|
|
// if let controller = self.environment?.controller() as? MediaCoverScreen, [.erase, .restore].contains(controller.mode), result == self.previewContainerView {
|
|
// return nil
|
|
// }
|
|
// return result
|
|
// }
|
|
|
|
func update(component: MediaCoverScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
|
self.environment = environment
|
|
|
|
guard let controller = environment.controller() as? MediaCoverScreen else {
|
|
return .zero
|
|
}
|
|
|
|
// let isFirstTime = self.component == nil
|
|
self.component = component
|
|
self.state = state
|
|
|
|
let isTablet: Bool
|
|
if case .regular = environment.metrics.widthClass {
|
|
isTablet = true
|
|
} else {
|
|
isTablet = false
|
|
}
|
|
|
|
let buttonSideInset: CGFloat = 16.0
|
|
var controlsBottomInset: CGFloat = 0.0
|
|
let previewSize: CGSize
|
|
var topInset: CGFloat = environment.statusBarHeight + 5.0
|
|
if isTablet {
|
|
let previewHeight = availableSize.height - topInset - 75.0
|
|
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
|
} else {
|
|
previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778))
|
|
if availableSize.height < previewSize.height + 30.0 {
|
|
topInset = 0.0
|
|
controlsBottomInset = -75.0
|
|
}
|
|
}
|
|
|
|
let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset))
|
|
let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset - 31.0), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset))
|
|
|
|
let cancelButtonSize = self.cancelButton.update(
|
|
transition: transition,
|
|
component: AnyComponent(Button(
|
|
content: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: .white)))
|
|
),
|
|
action: { [weak controller] in
|
|
controller?.requestDismiss(animated: true)
|
|
}
|
|
)),
|
|
environment: {},
|
|
containerSize: CGSize(width: 120.0, height: 44.0)
|
|
)
|
|
let cancelButtonFrame = CGRect(
|
|
origin: CGPoint(x: 16.0, y: 80.0),
|
|
size: cancelButtonSize
|
|
)
|
|
if let cancelButtonView = self.cancelButton.view {
|
|
if cancelButtonView.superview == nil {
|
|
self.addSubview(cancelButtonView)
|
|
setupButtonShadow(cancelButtonView)
|
|
}
|
|
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
|
}
|
|
|
|
let doneButtonSize = self.doneButton.update(
|
|
transition: transition,
|
|
component: AnyComponent(
|
|
ButtonComponent(
|
|
background: ButtonComponent.Background(
|
|
color: environment.theme.list.itemCheckColors.fillColor,
|
|
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
|
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
|
),
|
|
content: AnyComponentWithIdentity(
|
|
id: AnyHashable(0),
|
|
component: AnyComponent(ButtonTextContentComponent(
|
|
text: "Save Cover",
|
|
badge: 0,
|
|
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
|
badgeBackground: .clear,
|
|
badgeForeground: .clear
|
|
))
|
|
),
|
|
isEnabled: true,
|
|
displaysProgress: false,
|
|
action: { [weak controller, weak self] in
|
|
if let playerState = self?.state?.playerState, let mediaEditor = self?.component?.mediaEditor, let image = mediaEditor.resultImage {
|
|
mediaEditor.setCoverImageTimestamp(playerState.position)
|
|
controller?.completed(playerState.position, image)
|
|
}
|
|
controller?.requestDismiss(animated: true)
|
|
}
|
|
)
|
|
),
|
|
environment: {},
|
|
containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 50.0)
|
|
)
|
|
let doneButtonFrame = CGRect(
|
|
origin: CGPoint(x: floor((availableSize.width - doneButtonSize.width) / 2.0), y: availableSize.height - 99.0),
|
|
size: doneButtonSize
|
|
)
|
|
if let doneButtonView = self.doneButton.view {
|
|
if doneButtonView.superview == nil {
|
|
self.addSubview(doneButtonView)
|
|
}
|
|
transition.setFrame(view: doneButtonView, frame: doneButtonFrame)
|
|
}
|
|
|
|
let labelSize = self.label.update(
|
|
transition: transition,
|
|
component: AnyComponent(Text(text: "Story Cover", font: Font.semibold(17.0), color: UIColor(rgb: 0xffffff))),
|
|
environment: {},
|
|
containerSize: CGSize(width: availableSize.width - 88.0, height: 44.0)
|
|
)
|
|
let labelFrame = CGRect(
|
|
origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) / 2.0), y: 80.0),
|
|
size: labelSize
|
|
)
|
|
if let labelView = self.label.view {
|
|
if labelView.superview == nil {
|
|
self.addSubview(labelView)
|
|
setupButtonShadow(labelView)
|
|
}
|
|
if labelView.bounds.width > 0.0 && labelFrame.width != labelView.bounds.width {
|
|
if let snapshotView = labelView.snapshotView(afterScreenUpdates: false) {
|
|
snapshotView.center = labelView.center
|
|
self.buttonsContainerView.addSubview(snapshotView)
|
|
|
|
labelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
snapshotView.removeFromSuperview()
|
|
})
|
|
}
|
|
}
|
|
labelView.bounds = CGRect(origin: .zero, size: labelFrame.size)
|
|
transition.setPosition(view: labelView, position: labelFrame.center)
|
|
}
|
|
|
|
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
|
|
transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size))
|
|
|
|
transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame)
|
|
|
|
if let playerState = state.playerState {
|
|
let visibleTracks = playerState.tracks.filter { $0.id == 0 }.map { MediaScrubberComponent.Track($0) }
|
|
|
|
let mediaEditor = component.mediaEditor
|
|
let scrubberInset: CGFloat = buttonSideInset
|
|
let scrubberSize = self.scrubber.update(
|
|
transition: transition,
|
|
component: AnyComponent(MediaScrubberComponent(
|
|
context: component.context,
|
|
style: .cover,
|
|
theme: environment.theme,
|
|
generationTimestamp: playerState.generationTimestamp,
|
|
position: playerState.position,
|
|
minDuration: 1.0,
|
|
maxDuration: storyMaxVideoDuration,
|
|
isPlaying: playerState.isPlaying,
|
|
tracks: visibleTracks,
|
|
portalView: controller.portalView,
|
|
positionUpdated: { [weak mediaEditor] position, apply in
|
|
if let mediaEditor {
|
|
mediaEditor.seek(position, andPlay: false)
|
|
}
|
|
},
|
|
trackTrimUpdated: { _, _, _, _, _ in
|
|
},
|
|
trackOffsetUpdated: { _, _, _ in
|
|
},
|
|
trackLongPressed: { _, _ in
|
|
}
|
|
)),
|
|
environment: {},
|
|
containerSize: CGSize(width: previewSize.width - scrubberInset * 2.0, height: availableSize.height)
|
|
)
|
|
|
|
let scrubberFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - scrubberSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - scrubberSize.height + controlsBottomInset + 3.0 - 40.0), size: scrubberSize)
|
|
if let scrubberView = self.scrubber.view {
|
|
var animateIn = false
|
|
if scrubberView.superview == nil {
|
|
animateIn = true
|
|
self.addSubview(scrubberView)
|
|
}
|
|
if animateIn {
|
|
scrubberView.frame = scrubberFrame
|
|
} else {
|
|
transition.setFrame(view: scrubberView, frame: scrubberFrame)
|
|
}
|
|
if animateIn {
|
|
scrubberView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
scrubberView.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
|
|
}
|
|
}
|
|
}
|
|
|
|
return availableSize
|
|
}
|
|
}
|
|
|
|
func makeView() -> View {
|
|
return View()
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|
|
|
|
final class MediaCoverScreen: ViewController {
|
|
fileprivate final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate {
|
|
private weak var controller: MediaCoverScreen?
|
|
private let context: AccountContext
|
|
|
|
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
|
|
|
private var presentationData: PresentationData
|
|
private var validLayout: ContainerViewLayout?
|
|
|
|
init(controller: MediaCoverScreen) {
|
|
self.controller = controller
|
|
self.context = controller.context
|
|
|
|
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
|
|
|
|
super.init()
|
|
|
|
self.backgroundColor = .clear
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
self.view.disablesInteractiveModalDismiss = true
|
|
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
|
}
|
|
|
|
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func animateInFromEditor() {
|
|
if let view = self.componentHost.view as? MediaCoverScreenComponent.View {
|
|
view.animateInFromEditor()
|
|
}
|
|
}
|
|
|
|
func animateOutToEditor(completion: @escaping () -> Void) {
|
|
if let mediaEditor = self.controller?.mediaEditor {
|
|
mediaEditor.play()
|
|
}
|
|
if let view = self.componentHost.view as? MediaCoverScreenComponent.View {
|
|
view.animateOutToEditor(completion: completion)
|
|
}
|
|
}
|
|
|
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
let result = super.hitTest(point, with: event)
|
|
if result === self.view {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func requestLayout(transition: ComponentTransition) {
|
|
if let layout = self.validLayout {
|
|
self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition)
|
|
}
|
|
}
|
|
|
|
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: ComponentTransition) {
|
|
guard let controller = self.controller else {
|
|
return
|
|
}
|
|
let isFirstTime = self.validLayout == nil
|
|
self.validLayout = layout
|
|
|
|
let isTablet = layout.metrics.isTablet
|
|
|
|
let previewSize: CGSize
|
|
let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 5.0
|
|
if isTablet {
|
|
let previewHeight = layout.size.height - topInset - 75.0
|
|
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
|
} else {
|
|
previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778))
|
|
}
|
|
let bottomInset = layout.size.height - previewSize.height - topInset
|
|
|
|
let environment = ViewControllerComponentContainer.Environment(
|
|
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
|
navigationHeight: 0.0,
|
|
safeInsets: UIEdgeInsets(
|
|
top: topInset,
|
|
left: layout.safeInsets.left,
|
|
bottom: bottomInset,
|
|
right: layout.safeInsets.right
|
|
),
|
|
additionalInsets: layout.additionalInsets,
|
|
inputHeight: layout.inputHeight ?? 0.0,
|
|
metrics: layout.metrics,
|
|
deviceMetrics: layout.deviceMetrics,
|
|
orientation: nil,
|
|
isVisible: true,
|
|
theme: self.presentationData.theme,
|
|
strings: self.presentationData.strings,
|
|
dateTimeFormat: self.presentationData.dateTimeFormat,
|
|
controller: { [weak self] in
|
|
return self?.controller
|
|
}
|
|
)
|
|
|
|
let componentSize = self.componentHost.update(
|
|
transition: transition,
|
|
component: AnyComponent(
|
|
MediaCoverScreenComponent(
|
|
context: self.context,
|
|
mediaEditor: controller.mediaEditor
|
|
)
|
|
),
|
|
environment: {
|
|
environment
|
|
},
|
|
forceUpdate: forceUpdate || animateOut,
|
|
containerSize: layout.size
|
|
)
|
|
if let componentView = self.componentHost.view {
|
|
if componentView.superview == nil {
|
|
self.view.insertSubview(componentView, at: 3)
|
|
componentView.clipsToBounds = true
|
|
}
|
|
let componentFrame = CGRect(origin: .zero, size: componentSize)
|
|
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
|
}
|
|
|
|
if isFirstTime {
|
|
self.animateInFromEditor()
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate var node: Node {
|
|
return self.displayNode as! Node
|
|
}
|
|
|
|
fileprivate let context: AccountContext
|
|
fileprivate let mediaEditor: MediaEditor
|
|
fileprivate let previewView: MediaEditorPreviewView
|
|
fileprivate let portalView: PortalView
|
|
|
|
var completed: (Double, UIImage) -> Void = { _, _ in }
|
|
var dismissed: () -> Void = {}
|
|
|
|
private var initialValues: MediaEditorValues
|
|
|
|
init(
|
|
context: AccountContext,
|
|
mediaEditor: MediaEditor,
|
|
previewView: MediaEditorPreviewView,
|
|
portalView: PortalView
|
|
) {
|
|
self.context = context
|
|
self.mediaEditor = mediaEditor
|
|
self.previewView = previewView
|
|
self.portalView = portalView
|
|
self.initialValues = mediaEditor.values.makeCopy()
|
|
|
|
super.init(navigationBarPresentationData: nil)
|
|
self.navigationPresentation = .flatModal
|
|
|
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
|
|
|
self.statusBar.statusBarStyle = .White
|
|
|
|
if let coverImageTimestamp = mediaEditor.values.coverImageTimestamp {
|
|
mediaEditor.seek(coverImageTimestamp, andPlay: false)
|
|
} else {
|
|
mediaEditor.seek(0.0, andPlay: false)
|
|
}
|
|
}
|
|
|
|
required init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func loadDisplayNode() {
|
|
self.displayNode = Node(controller: self)
|
|
|
|
super.displayNodeDidLoad()
|
|
}
|
|
|
|
func requestDismiss(animated: Bool) {
|
|
self.dismissed()
|
|
|
|
self.node.animateOutToEditor(completion: {
|
|
self.dismiss()
|
|
})
|
|
}
|
|
|
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
|
|
}
|
|
}
|
|
|
|
private func setupButtonShadow(_ view: UIView, radius: CGFloat = 2.0) {
|
|
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
|
view.layer.shadowRadius = radius
|
|
view.layer.shadowColor = UIColor.black.cgColor
|
|
view.layer.shadowOpacity = 0.35
|
|
}
|