import Foundation import UIKit import Display import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext import AsyncDisplayKit import ComponentDisplayAdapters import LottieAnimationComponent import EmojiStatusComponent import LottieComponent import LottieComponentEmojiContent import AudioToolbox private final class RoundMaskView: UIImageView { private var currentDiameter: CGFloat? func update(diameter: CGFloat) { if self.currentDiameter != diameter { self.currentDiameter = diameter let shadowWidth: CGFloat = 6.0 self.image = generateImage(CGSize(width: shadowWidth * 2.0 + diameter, height: diameter), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) let shadowColor = UIColor.black let stepCount = 10 var colors: [CGColor] = [] var locations: [CGFloat] = [] for i in 0 ... stepCount { let t = CGFloat(i) / CGFloat(stepCount) colors.append(shadowColor.withAlphaComponent(t * t).cgColor) locations.append(t) } let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)! let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0) let gradientWidth = shadowWidth context.drawRadialGradient(gradient, startCenter: center, startRadius: size.width / 2.0, endCenter: center, endRadius: size.width / 2.0 - gradientWidth, options: []) context.setFillColor(shadowColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowWidth, y: 0.0), size: CGSize(width: size.height, height: size.height)).insetBy(dx: -0.5, dy: -0.5)) })?.stretchableImage(withLeftCapWidth: Int(shadowWidth * 0.5 + diameter * 0.5), topCapHeight: Int(diameter * 0.5)) } } } private final class HoldGestureRecognizer: UITapGestureRecognizer { private var currentHighlightPoint: CGPoint? var updateHighlight: ((CGPoint?) -> Void)? override var state: UIGestureRecognizer.State { didSet { print("set state \(self.state)") } } override func reset() { super.reset() if let _ = self.currentHighlightPoint { self.currentHighlightPoint = nil self.updateHighlight?(nil) } } override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) let point = touches.first?.location(in: self.view) if self.currentHighlightPoint == nil { self.currentHighlightPoint = point self.updateHighlight?(point) } } override func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) } override func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) } } final class EmojiSearchSearchBarComponent: Component { enum TextInputState: Equatable { case inactive case active(hasText: Bool) } let context: AccountContext let theme: PresentationTheme let forceNeedsVibrancy: Bool let strings: PresentationStrings let useOpaqueTheme: Bool let textInputState: TextInputState let categories: EmojiSearchCategories? let searchTermUpdated: (EmojiSearchCategories.Group?) -> Void let activateTextInput: () -> Void init( context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, useOpaqueTheme: Bool, textInputState: TextInputState, categories: EmojiSearchCategories?, searchTermUpdated: @escaping (EmojiSearchCategories.Group?) -> Void, activateTextInput: @escaping () -> Void ) { self.context = context self.theme = theme self.forceNeedsVibrancy = forceNeedsVibrancy self.strings = strings self.useOpaqueTheme = useOpaqueTheme self.textInputState = textInputState self.categories = categories self.searchTermUpdated = searchTermUpdated self.activateTextInput = activateTextInput } static func ==(lhs: EmojiSearchSearchBarComponent, rhs: EmojiSearchSearchBarComponent) -> Bool { if lhs.theme !== rhs.theme { return false } if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy { return false } if lhs.strings !== rhs.strings { return false } if lhs.useOpaqueTheme != rhs.useOpaqueTheme { return false } if lhs.textInputState != rhs.textInputState { return false } if lhs.categories != rhs.categories { return false } return true } private struct ItemLayout { let containerSize: CGSize let itemCount: Int let itemSize: CGSize let itemSpacing: CGFloat let contentSize: CGSize let leftInset: CGFloat let rightInset: CGFloat let itemStartX: CGFloat let textSpacing: CGFloat let textFrame: CGRect init(containerSize: CGSize, textSize: CGSize, itemCount: Int) { self.containerSize = containerSize self.itemCount = itemCount self.itemSpacing = 11.0 self.leftInset = 8.0 self.rightInset = 8.0 self.itemSize = CGSize(width: 24.0, height: 24.0) self.textSpacing = 11.0 self.textFrame = CGRect(origin: CGPoint(x: self.leftInset, y: floor((containerSize.height - textSize.height) * 0.5)), size: textSize) let itemsWidth: CGFloat = self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1)) var itemStartX = self.textFrame.maxX + self.textSpacing if itemStartX + itemsWidth + self.rightInset < containerSize.width { itemStartX = containerSize.width - self.rightInset - itemsWidth } self.itemStartX = itemStartX self.contentSize = CGSize(width: self.itemStartX + itemsWidth + self.rightInset, height: containerSize.height) } func visibleItems(for rect: CGRect) -> Range? { let baseItemX: CGFloat = self.itemStartX let offsetRect = rect.offsetBy(dx: -baseItemX, dy: 0.0) var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize.width + self.itemSpacing))) minVisibleIndex = max(0, minVisibleIndex) var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize.height + self.itemSpacing))) maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1) if minVisibleIndex <= maxVisibleIndex { return minVisibleIndex ..< (maxVisibleIndex + 1) } else { return nil } } func frame(at index: Int) -> CGRect { return CGRect(origin: CGPoint(x: self.itemStartX + CGFloat(index) * (self.itemSize.width + self.itemSpacing), y: floor((self.containerSize.height - self.itemSize.height) * 0.5)), size: self.itemSize) } } private final class ContentScrollView: UIScrollView, PagerExpandableScrollView { override static var layerClass: AnyClass { return EmojiPagerContentComponent.View.ContentScrollLayer.self } private let mirrorView: UIView init(mirrorView: UIView) { self.mirrorView = mirrorView super.init(frame: CGRect()) (self.layer as? EmojiPagerContentComponent.View.ContentScrollLayer)?.mirrorLayer = mirrorView.layer self.canCancelContentTouches = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func touchesShouldCancel(in view: UIView) -> Bool { return true } } private final class ItemView { let view = ComponentView() let tintView = UIImageView() init() { } } final class View: UIView, UIScrollViewDelegate { let tintContainerView: UIView private let scrollView: ContentScrollView private let tintScrollView: UIView private let textView = ComponentView() private let textContainerView: UIView private let tintTextView = ComponentView() private let tintTextContainerView: UIView private var visibleItemViews: [AnyHashable: ItemView] = [:] private let selectedItemBackground: SimpleLayer private let selectedItemTintBackground: SimpleLayer private var component: EmojiSearchSearchBarComponent? private weak var componentState: EmptyComponentState? private var itemLayout: ItemLayout? private var ignoreScrolling: Bool = false private let roundMaskView: RoundMaskView private let tintRoundMaskView: RoundMaskView private var highlightedItem: AnyHashable? private var selectedItem: AnyHashable? private var disableInteraction: Bool = false private lazy var hapticFeedback: HapticFeedback = { return HapticFeedback() }() override init(frame: CGRect) { self.tintContainerView = UIView() self.tintScrollView = UIView() self.tintScrollView.clipsToBounds = true self.scrollView = ContentScrollView(mirrorView: self.tintScrollView) self.textContainerView = UIView() self.textContainerView.isUserInteractionEnabled = false self.tintTextContainerView = UIView() self.tintTextContainerView.isUserInteractionEnabled = false self.roundMaskView = RoundMaskView() self.tintRoundMaskView = RoundMaskView() self.selectedItemBackground = SimpleLayer() self.selectedItemTintBackground = SimpleLayer() super.init(frame: frame) self.scrollView.delaysContentTouches = false if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { self.scrollView.contentInsetAdjustmentBehavior = .never } if #available(iOS 13.0, *) { self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false } self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.delegate = self self.scrollView.clipsToBounds = true self.scrollView.scrollsToTop = false self.addSubview(self.scrollView) self.addSubview(self.textContainerView) self.tintContainerView.addSubview(self.tintScrollView) self.tintContainerView.addSubview(self.tintTextContainerView) self.mask = self.roundMaskView self.tintContainerView.mask = self.tintRoundMaskView self.scrollView.layer.addSublayer(self.selectedItemBackground) self.tintScrollView.layer.addSublayer(self.selectedItemTintBackground) let tapRecognizer = HoldGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) tapRecognizer.updateHighlight = { [weak self] point in guard let self else { return } var highlightedItem: AnyHashable? if let point = point { let location = self.convert(point, to: self.scrollView) for (id, itemView) in self.visibleItemViews { if let itemComponentView = itemView.view.view, itemComponentView.frame.contains(location) { highlightedItem = id break } } } if self.highlightedItem != highlightedItem { self.highlightedItem = highlightedItem self.componentState?.updated(transition: .easeInOut(duration: 0.2)) } } self.addGestureRecognizer(tapRecognizer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { guard let component = self.component, let itemLayout = self.itemLayout else { return } let location = recognizer.location(in: self.scrollView) if (component.categories?.groups ?? []).isEmpty || location.x <= itemLayout.itemStartX - itemLayout.textSpacing { component.activateTextInput() } else { for (id, itemView) in self.visibleItemViews { if let itemComponentView = itemView.view.view, itemComponentView.frame.contains(location), let itemId = id.base as? Int64 { if self.selectedItem == AnyHashable(id) { self.selectedItem = nil } else { self.selectedItem = AnyHashable(id) AudioServicesPlaySystemSound(0x450) self.hapticFeedback.tap() } self.componentState?.updated(transition: .easeInOut(duration: 0.2)) if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) { component.searchTermUpdated(group) if let itemComponentView = itemView.view.view { var offset = self.scrollView.contentOffset.x let maxDistance: CGFloat = 44.0 if itemComponentView.frame.maxX - offset > self.scrollView.bounds.width - maxDistance { offset = itemComponentView.frame.maxX - (self.scrollView.bounds.width - maxDistance) } if itemComponentView.frame.minX - offset < maxDistance { offset = itemComponentView.frame.minX - maxDistance } offset = max(0.0, min(offset, self.scrollView.contentSize.width - self.scrollView.bounds.width)) if offset != self.scrollView.contentOffset.x { self.scrollView.setContentOffset(CGPoint(x: offset, y: 0.0), animated: true) } } } else { let transition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)) transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) self.updateScrolling(transition: transition, fromScrolling: false) //self.scrollView.setContentOffset(CGPoint(), animated: true) component.searchTermUpdated(nil) } break } } } } } func clearSelection(dispatchEvent: Bool) { if self.selectedItem != nil { self.selectedItem = nil let transition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)) transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) self.updateScrolling(transition: transition, fromScrolling: false) self.componentState?.updated(transition: transition) if dispatchEvent { self.component?.searchTermUpdated(nil) } } } func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate, fromScrolling: true) } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.disableInteraction { for (_, itemView) in self.visibleItemViews { if let itemComponentView = itemView.view.view { if itemComponentView.bounds.contains(self.convert(point, to: itemComponentView)) { return self } } } return nil } return super.hitTest(point, with: event) } func leftTextPosition() -> CGFloat { guard let itemLayout = self.itemLayout else { return 0.0 } let visibleBounds = self.scrollView.bounds return (itemLayout.itemStartX - itemLayout.textSpacing) + visibleBounds.minX } private func updateScrolling(transition: ComponentTransition, fromScrolling: Bool) { guard let component = self.component, let itemLayout = self.itemLayout else { return } let itemAlpha: CGFloat switch component.textInputState { case let .active(hasText): if hasText { itemAlpha = 0.0 } else { itemAlpha = 1.0 } case .inactive: itemAlpha = 1.0 } var validItemIds = Set() let visibleBounds = self.scrollView.bounds var animateAppearingItems = false if fromScrolling { animateAppearingItems = true } let items = component.categories?.groups ?? [] for i in 0 ..< items.count { let itemFrame = itemLayout.frame(at: i) if visibleBounds.intersects(itemFrame) { let item = items[i] validItemIds.insert(AnyHashable(item.id)) var animateItem = false var itemTransition = transition let itemView: ItemView if let current = self.visibleItemViews[AnyHashable(item.id)] { itemView = current } else { animateItem = animateAppearingItems itemTransition = .immediate itemView = ItemView() self.visibleItemViews[AnyHashable(item.id)] = itemView } let color: UIColor if component.theme.overallDarkAppearance && component.forceNeedsVibrancy { let tempColor = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor color = tempColor.withMultipliedAlpha(0.3) } else if component.useOpaqueTheme { color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor } else { color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor } let _ = itemView.view.update( transition: .immediate, component: AnyComponent(LottieComponent( content: LottieComponent.EmojiContent( context: component.context, fileId: item.id ), color: color )), environment: {}, containerSize: itemLayout.itemSize ) itemView.tintView.tintColor = .black if let view = itemView.view.view as? LottieComponent.View { if view.superview == nil { self.scrollView.addSubview(view) view.output = itemView.tintView self.tintScrollView.addSubview(itemView.tintView) } itemTransition.setPosition(view: view, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY)) itemTransition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: CGSize(width: itemLayout.itemSize.width, height: itemLayout.itemSize.height))) var scaleFactor = itemFrame.width / itemLayout.itemSize.width if self.highlightedItem == AnyHashable(item.id) { scaleFactor *= 0.8 } itemTransition.setScale(view: view, scale: scaleFactor) itemTransition.setPosition(view: itemView.tintView, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY)) itemTransition.setBounds(view: itemView.tintView, bounds: CGRect(origin: CGPoint(), size: CGSize(width: itemLayout.itemSize.width, height: itemLayout.itemSize.height))) itemTransition.setScale(view: itemView.tintView, scale: scaleFactor) itemTransition.setAlpha(view: view, alpha: itemAlpha) itemTransition.setAlpha(view: itemView.tintView, alpha: itemAlpha) let isHidden = !visibleBounds.intersects(itemFrame) if isHidden != view.isHidden { view.isHidden = isHidden itemView.tintView.isHidden = true if !isHidden { view.playOnce() } } else if animateItem { if fromScrolling { view.playOnce(delay: 0.08) } } } } } var removedItemIds: [AnyHashable] = [] for (id, itemView) in self.visibleItemViews { if !validItemIds.contains(id) { removedItemIds.append(id) if let itemComponentView = itemView.view.view { transition.attachAnimation(view: itemComponentView, id: "remove", completion: { [weak itemComponentView] _ in itemComponentView?.removeFromSuperview() }) } let tintView = itemView.tintView transition.attachAnimation(view: tintView, id: "remove", completion: { [weak tintView] _ in tintView?.removeFromSuperview() }) //itemView.view.view?.removeFromSuperview() //itemView.tintView.removeFromSuperview() } } for id in removedItemIds { self.visibleItemViews.removeValue(forKey: id) } let selectedColor: UIColor if component.theme.overallDarkAppearance && component.forceNeedsVibrancy { let tempColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor selectedColor = tempColor.withMultipliedAlpha(0.3) } else { selectedColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor } if let selectedItem = self.selectedItem, let index = items.firstIndex(where: { AnyHashable($0.id) == selectedItem }) { let selectedItemCenter = itemLayout.frame(at: index).center let selectionSize = CGSize(width: 28.0, height: 28.0) self.selectedItemBackground.backgroundColor = selectedColor.cgColor self.selectedItemTintBackground.backgroundColor = UIColor(white: 0.0, alpha: 0.15).cgColor self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5 self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5 let selectionFrame = CGRect(origin: CGPoint(x: floor(selectedItemCenter.x - selectionSize.width * 0.5), y: floor(selectedItemCenter.y - selectionSize.height * 0.5)), size: selectionSize) self.selectedItemBackground.bounds = CGRect(origin: CGPoint(), size: selectionFrame.size) self.selectedItemTintBackground.bounds = CGRect(origin: CGPoint(), size: selectionFrame.size) if self.selectedItemBackground.opacity == 0.0 { self.selectedItemBackground.position = selectionFrame.center self.selectedItemTintBackground.position = selectionFrame.center self.selectedItemBackground.opacity = 1.0 self.selectedItemTintBackground.opacity = 1.0 ComponentTransition.immediate.setScale(layer: self.selectedItemBackground, scale: 1.0) ComponentTransition.immediate.setScale(layer: self.selectedItemTintBackground, scale: 1.0) if !transition.animation.isImmediate { self.selectedItemBackground.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.selectedItemTintBackground.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.selectedItemBackground.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, damping: 92.0) self.selectedItemTintBackground.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, damping: 92.0) } } else { if self.selectedItemBackground.position != selectionFrame.center { transition.setPosition(layer: self.selectedItemBackground, position: selectionFrame.center) transition.setPosition(layer: self.selectedItemTintBackground, position: selectionFrame.center) if case let .curve(duration, _) = transition.animation { ComponentTransition.immediate.setScale(layer: self.selectedItemBackground, scale: 1.0) ComponentTransition.immediate.setScale(layer: self.selectedItemTintBackground, scale: 1.0) self.selectedItemBackground.animateKeyframes(values: [1.0 as NSNumber, 0.75 as NSNumber, 1.0 as NSNumber], duration: duration, keyPath: "transform.scale") self.selectedItemTintBackground.animateKeyframes(values: [1.0 as NSNumber, 0.75 as NSNumber, 1.0 as NSNumber], duration: duration, keyPath: "transform.scale") } else { transition.setScale(layer: self.selectedItemBackground, scale: 1.0) transition.setScale(layer: self.selectedItemTintBackground, scale: 1.0) } } } } else { transition.setAlpha(layer: self.selectedItemBackground, alpha: 0.0) transition.setScale(layer: self.selectedItemBackground, scale: 0.8) transition.setAlpha(layer: self.selectedItemTintBackground, alpha: 0.0) transition.setScale(layer: self.selectedItemTintBackground, scale: 0.8) } let scrollBounds = self.scrollView.bounds let textOffset = max(0.0, scrollBounds.minX - (itemLayout.itemStartX - itemLayout.textFrame.maxX - itemLayout.textSpacing)) transition.setPosition(view: self.textContainerView, position: self.scrollView.center) transition.setBounds(view: self.textContainerView, bounds: CGRect(origin: CGPoint(x: textOffset, y: 0.0), size: scrollBounds.size)) transition.setPosition(view: self.tintTextContainerView, position: self.scrollView.center) transition.setBounds(view: self.tintTextContainerView, bounds: CGRect(origin: CGPoint(x: textOffset, y: 0.0), size: scrollBounds.size)) } func update(component: EmojiSearchSearchBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.componentState = state let textColor: UIColor if component.theme.overallDarkAppearance && component.forceNeedsVibrancy { textColor = component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3) } else { textColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor } let textSize = self.textView.update( transition: .immediate, component: AnyComponent(Text( text: component.strings.Common_Search, font: Font.regular(17.0), color: textColor )), environment: {}, containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0) ) let _ = self.tintTextView.update( transition: .immediate, component: AnyComponent(Text( text: component.strings.Common_Search, font: Font.regular(17.0), color: .black )), environment: {}, containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0) ) let itemLayout = ItemLayout(containerSize: availableSize, textSize: textSize, itemCount: component.categories?.groups.count ?? 0) self.itemLayout = itemLayout if let textComponentView = self.textView.view { if textComponentView.superview == nil { self.textContainerView.addSubview(textComponentView) } transition.setFrame(view: textComponentView, frame: itemLayout.textFrame) } if let tintTextComponentView = self.tintTextView.view { if tintTextComponentView.superview == nil { self.tintTextContainerView.addSubview(tintTextComponentView) } transition.setFrame(view: tintTextComponentView, frame: itemLayout.textFrame) } self.ignoreScrolling = true if self.scrollView.bounds.size != availableSize { transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize)) } if case .active(true) = component.textInputState { transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) } if self.scrollView.contentSize != itemLayout.contentSize { self.scrollView.contentSize = itemLayout.contentSize } self.ignoreScrolling = false let maskFrame = CGRect(origin: CGPoint(), size: availableSize) transition.setFrame(view: self.roundMaskView, frame: maskFrame) self.roundMaskView.update(diameter: maskFrame.height) transition.setFrame(view: self.tintRoundMaskView, frame: maskFrame) self.tintRoundMaskView.update(diameter: maskFrame.height) self.updateScrolling(transition: transition, fromScrolling: false) switch component.textInputState { case let .active(hasText): if hasText { self.disableInteraction = false self.isUserInteractionEnabled = false } else { self.disableInteraction = true self.isUserInteractionEnabled = true } self.textView.view?.isHidden = hasText self.tintTextView.view?.isHidden = hasText case .inactive: self.disableInteraction = false self.isUserInteractionEnabled = true self.textView.view?.isHidden = false self.tintTextView.view?.isHidden = false } return availableSize } } func makeView() -> View { return View(frame: CGRect()) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }