| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- import Cocoa
- // MARK: - Plan Model
- enum PaywallPlan: CaseIterable {
- case monthly
- case yearly
- case lifetime
- var title: String {
- switch self {
- case .monthly: "Monthly"
- case .yearly: "Yearly"
- case .lifetime: "Lifetime"
- }
- }
- var subtitle: String {
- switch self {
- case .monthly: "$4.99 / month, cancel anytime"
- case .yearly: "Eligible new subscribers get 7 days free, then $29.99 / year"
- case .lifetime: "$99.99 once, lifetime access"
- }
- }
- var price: String {
- switch self {
- case .monthly: "$4.99"
- case .yearly: "$29.99"
- case .lifetime: "$99.99"
- }
- }
- var ctaTitle: String {
- switch self {
- case .monthly: "Subscribe for $4.99 / Month"
- case .yearly: "Start 7-Day Free Trial"
- case .lifetime: "Buy Lifetime Access"
- }
- }
- }
- // MARK: - Left Panel
- private final class PaywallLeftPanelView: NSView {
- private let gradientLayer = CAGradientLayer()
- override init(frame frameRect: NSRect) {
- super.init(frame: frameRect)
- wantsLayer = true
- gradientLayer.colors = [
- NSColor(red: 0.88, green: 0.94, blue: 1.0, alpha: 1).cgColor,
- NSColor(red: 0.95, green: 0.97, blue: 1.0, alpha: 1).cgColor,
- ]
- gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
- gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
- layer?.insertSublayer(gradientLayer, at: 0)
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- override func layout() {
- super.layout()
- gradientLayer.frame = bounds
- let mask = CAShapeLayer()
- mask.path = CGPath(
- roundedRect: bounds,
- cornerWidth: 20,
- cornerHeight: 20,
- transform: nil
- )
- layer?.mask = mask
- }
- }
- // MARK: - Badge
- private final class PaywallBadgeView: NSView {
- init(text: String, iconName: String, background: NSColor, foreground: NSColor) {
- super.init(frame: .zero)
- translatesAutoresizingMaskIntoConstraints = false
- wantsLayer = true
- layer?.backgroundColor = background.cgColor
- layer?.cornerRadius = 10
- layer?.masksToBounds = true
- let icon = NSImageView()
- icon.translatesAutoresizingMaskIntoConstraints = false
- if let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) {
- let config = NSImage.SymbolConfiguration(pointSize: 9, weight: .semibold)
- icon.image = image.withSymbolConfiguration(config)
- }
- icon.contentTintColor = foreground
- let label = NSTextField(labelWithString: text)
- label.font = AppTheme.semiboldFont(size: 10)
- label.textColor = foreground
- label.translatesAutoresizingMaskIntoConstraints = false
- addSubview(icon)
- addSubview(label)
- NSLayoutConstraint.activate([
- heightAnchor.constraint(equalToConstant: 20),
- icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
- icon.centerYAnchor.constraint(equalTo: centerYAnchor),
- icon.widthAnchor.constraint(equalToConstant: 12),
- icon.heightAnchor.constraint(equalToConstant: 12),
- label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 4),
- label.centerYAnchor.constraint(equalTo: centerYAnchor),
- label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
- ])
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- }
- // MARK: - Feature Row
- private final class PaywallFeatureRow: NSView {
- init(text: String) {
- super.init(frame: .zero)
- translatesAutoresizingMaskIntoConstraints = false
- let checkContainer = NSView()
- checkContainer.translatesAutoresizingMaskIntoConstraints = false
- checkContainer.wantsLayer = true
- checkContainer.layer?.backgroundColor = AppTheme.green.cgColor
- checkContainer.layer?.cornerRadius = 10
- checkContainer.layer?.masksToBounds = true
- let checkIcon = NSImageView()
- checkIcon.translatesAutoresizingMaskIntoConstraints = false
- if let image = NSImage(systemSymbolName: "checkmark", accessibilityDescription: nil) {
- let config = NSImage.SymbolConfiguration(pointSize: 9, weight: .bold)
- checkIcon.image = image.withSymbolConfiguration(config)
- }
- checkIcon.contentTintColor = .white
- let label = NSTextField(labelWithString: text)
- label.font = AppTheme.regularFont(size: 14)
- label.textColor = AppTheme.navy
- label.translatesAutoresizingMaskIntoConstraints = false
- addSubview(checkContainer)
- checkContainer.addSubview(checkIcon)
- addSubview(label)
- NSLayoutConstraint.activate([
- heightAnchor.constraint(equalToConstant: 28),
- checkContainer.leadingAnchor.constraint(equalTo: leadingAnchor),
- checkContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
- checkContainer.widthAnchor.constraint(equalToConstant: 20),
- checkContainer.heightAnchor.constraint(equalToConstant: 20),
- checkIcon.centerXAnchor.constraint(equalTo: checkContainer.centerXAnchor),
- checkIcon.centerYAnchor.constraint(equalTo: checkContainer.centerYAnchor),
- checkIcon.widthAnchor.constraint(equalToConstant: 12),
- checkIcon.heightAnchor.constraint(equalToConstant: 12),
- label.leadingAnchor.constraint(equalTo: checkContainer.trailingAnchor, constant: 12),
- label.centerYAnchor.constraint(equalTo: centerYAnchor),
- label.trailingAnchor.constraint(equalTo: trailingAnchor),
- ])
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- }
- // MARK: - Plan Card
- private final class PaywallPlanCard: NSControl {
- var onSelect: (() -> Void)?
- private let plan: PaywallPlan
- private let titleLabel = NSTextField(labelWithString: "")
- private let subtitleLabel = NSTextField(labelWithString: "")
- private let priceLabel = NSTextField(labelWithString: "")
- private var badgeView: PaywallBadgeView?
- var isChosen: Bool = false {
- didSet { updateAppearance() }
- }
- init(plan: PaywallPlan) {
- self.plan = plan
- super.init(frame: .zero)
- translatesAutoresizingMaskIntoConstraints = false
- wantsLayer = true
- layer?.cornerRadius = 12
- layer?.backgroundColor = NSColor.white.cgColor
- layer?.masksToBounds = false
- titleLabel.stringValue = plan.title
- titleLabel.font = AppTheme.semiboldFont(size: 15)
- titleLabel.translatesAutoresizingMaskIntoConstraints = false
- subtitleLabel.stringValue = plan.subtitle
- subtitleLabel.font = AppTheme.regularFont(size: 11)
- subtitleLabel.textColor = AppTheme.textSecondary
- subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
- priceLabel.stringValue = plan.price
- priceLabel.font = AppTheme.semiboldFont(size: 15)
- priceLabel.alignment = .right
- priceLabel.translatesAutoresizingMaskIntoConstraints = false
- addSubview(titleLabel)
- addSubview(subtitleLabel)
- addSubview(priceLabel)
- NSLayoutConstraint.activate([
- heightAnchor.constraint(equalToConstant: 86),
- titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
- titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 24),
- subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
- subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3),
- subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: priceLabel.leadingAnchor, constant: -12),
- priceLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
- priceLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
- ])
- if plan == .yearly {
- let badge = PaywallBadgeView(
- text: "7 Days Free Trial",
- iconName: "calendar",
- background: AppTheme.paywallPink,
- foreground: AppTheme.paywallPinkText
- )
- badgeView = badge
- addSubview(badge)
- NSLayoutConstraint.activate([
- badge.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
- badge.topAnchor.constraint(equalTo: topAnchor, constant: 8),
- ])
- } else if plan == .lifetime {
- let badge = PaywallBadgeView(
- text: "Best Value",
- iconName: "star.fill",
- background: AppTheme.paywallGold,
- foreground: AppTheme.paywallGoldText
- )
- badgeView = badge
- addSubview(badge)
- NSLayoutConstraint.activate([
- badge.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
- badge.topAnchor.constraint(equalTo: topAnchor, constant: 8),
- ])
- }
- updateAppearance()
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- private func updateAppearance() {
- let titleColor = isChosen && plan == .yearly ? AppTheme.green : AppTheme.navy
- titleLabel.textColor = titleColor
- priceLabel.textColor = titleColor
- layer?.borderWidth = isChosen ? 2 : 1
- layer?.borderColor = (isChosen ? AppTheme.green : AppTheme.paywallBorder).cgColor
- }
- override func mouseUp(with event: NSEvent) {
- guard bounds.contains(convert(event.locationInWindow, from: nil)) else { return }
- onSelect?()
- }
- override func resetCursorRects() {
- addCursorRect(bounds, cursor: .pointingHand)
- }
- }
- // MARK: - Close Button
- private final class PaywallCloseButton: NSButton {
- var onClose: (() -> Void)?
- init() {
- super.init(frame: .zero)
- isBordered = false
- translatesAutoresizingMaskIntoConstraints = false
- wantsLayer = true
- layer?.backgroundColor = AppTheme.blueLight.cgColor
- layer?.cornerRadius = 14
- if let image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Close") {
- let config = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
- self.image = image.withSymbolConfiguration(config)
- }
- contentTintColor = AppTheme.blue
- target = self
- action = #selector(tapped)
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- @objc private func tapped() {
- onClose?()
- }
- override func resetCursorRects() {
- addCursorRect(bounds, cursor: .pointingHand)
- }
- }
- // MARK: - Footer Link
- private final class PaywallFooterLink: NSButton {
- init(title: String) {
- super.init(frame: .zero)
- self.title = title
- isBordered = false
- font = AppTheme.regularFont(size: 11)
- contentTintColor = AppTheme.textSecondary
- translatesAutoresizingMaskIntoConstraints = false
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- override func resetCursorRects() {
- addCursorRect(bounds, cursor: .pointingHand)
- }
- }
- // MARK: - Main Paywall Card
- final class PaywallView: NSView {
- var onClose: (() -> Void)?
- var onPurchase: ((PaywallPlan) -> Void)?
- var onRestore: (() -> Void)?
- private var selectedPlan: PaywallPlan = .yearly
- private var planCards: [PaywallPlan: PaywallPlanCard] = [:]
- private let ctaButton = NSButton()
- init() {
- super.init(frame: .zero)
- translatesAutoresizingMaskIntoConstraints = false
- wantsLayer = true
- layer?.backgroundColor = NSColor.white.cgColor
- layer?.cornerRadius = 0
- setup()
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- private func setup() {
- let leftPanel = makeLeftPanel()
- let rightPanel = makeRightPanel()
- addSubview(leftPanel)
- addSubview(rightPanel)
- NSLayoutConstraint.activate([
- leftPanel.leadingAnchor.constraint(equalTo: leadingAnchor),
- leftPanel.topAnchor.constraint(equalTo: topAnchor),
- leftPanel.bottomAnchor.constraint(equalTo: bottomAnchor),
- leftPanel.widthAnchor.constraint(equalToConstant: 320),
- rightPanel.leadingAnchor.constraint(equalTo: leftPanel.trailingAnchor),
- rightPanel.trailingAnchor.constraint(equalTo: trailingAnchor),
- rightPanel.topAnchor.constraint(equalTo: topAnchor),
- rightPanel.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
- }
- private func makeLeftPanel() -> NSView {
- let panel = PaywallLeftPanelView()
- panel.translatesAutoresizingMaskIntoConstraints = false
- let title = NSTextField(labelWithString: "Unlock Your Full\nPrinting Potential")
- title.font = AppTheme.semiboldFont(size: 22)
- title.textColor = AppTheme.navy
- title.maximumNumberOfLines = 2
- title.translatesAutoresizingMaskIntoConstraints = false
- let featuresStack = NSStackView()
- featuresStack.orientation = .vertical
- featuresStack.spacing = 6
- featuresStack.alignment = .leading
- featuresStack.translatesAutoresizingMaskIntoConstraints = false
- let features = [
- "Unlimited high-quality scans",
- "Advanced OCR technology",
- "Direct cloud printing",
- "Ad-free experience",
- "Priority support",
- "Secure storage",
- ]
- for feature in features {
- featuresStack.addArrangedSubview(PaywallFeatureRow(text: feature))
- }
- panel.addSubview(title)
- panel.addSubview(featuresStack)
- NSLayoutConstraint.activate([
- title.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 28),
- title.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
- title.topAnchor.constraint(equalTo: panel.topAnchor, constant: 48),
- featuresStack.leadingAnchor.constraint(equalTo: title.leadingAnchor),
- featuresStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
- featuresStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 28),
- ])
- return panel
- }
- private func makeRightPanel() -> NSView {
- let panel = NSView()
- panel.translatesAutoresizingMaskIntoConstraints = false
- let closeButton = PaywallCloseButton()
- closeButton.onClose = { [weak self] in self?.onClose?() }
- let title = NSTextField(labelWithString: "Go Premium")
- title.font = AppTheme.semiboldFont(size: 26)
- title.textColor = AppTheme.navy
- title.translatesAutoresizingMaskIntoConstraints = false
- let subtitle = NSTextField(labelWithString: "Experience professional quality printing and scanning without limits.")
- subtitle.font = AppTheme.regularFont(size: 13)
- subtitle.textColor = AppTheme.textSecondary
- subtitle.maximumNumberOfLines = 2
- subtitle.translatesAutoresizingMaskIntoConstraints = false
- let plansStack = NSStackView()
- plansStack.orientation = .vertical
- plansStack.spacing = 12
- plansStack.translatesAutoresizingMaskIntoConstraints = false
- for plan in PaywallPlan.allCases {
- let card = PaywallPlanCard(plan: plan)
- card.isChosen = plan == selectedPlan
- card.onSelect = { [weak self] in self?.selectPlan(plan) }
- planCards[plan] = card
- plansStack.addArrangedSubview(card)
- }
- ctaButton.title = selectedPlan.ctaTitle
- ctaButton.isBordered = false
- ctaButton.wantsLayer = true
- ctaButton.layer?.backgroundColor = AppTheme.navy.cgColor
- ctaButton.layer?.cornerRadius = 12
- ctaButton.font = AppTheme.semiboldFont(size: 15)
- ctaButton.contentTintColor = .white
- ctaButton.target = self
- ctaButton.action = #selector(purchaseTapped)
- ctaButton.translatesAutoresizingMaskIntoConstraints = false
- let footer = makeFooter()
- panel.addSubview(closeButton)
- panel.addSubview(title)
- panel.addSubview(subtitle)
- panel.addSubview(plansStack)
- panel.addSubview(ctaButton)
- panel.addSubview(footer)
- NSLayoutConstraint.activate([
- closeButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
- closeButton.topAnchor.constraint(equalTo: panel.topAnchor, constant: 20),
- closeButton.widthAnchor.constraint(equalToConstant: 28),
- closeButton.heightAnchor.constraint(equalToConstant: 28),
- title.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 28),
- title.topAnchor.constraint(equalTo: panel.topAnchor, constant: 40),
- title.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -12),
- subtitle.leadingAnchor.constraint(equalTo: title.leadingAnchor),
- subtitle.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
- subtitle.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 6),
- plansStack.leadingAnchor.constraint(equalTo: title.leadingAnchor),
- plansStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
- plansStack.topAnchor.constraint(equalTo: subtitle.bottomAnchor, constant: 24),
- ctaButton.leadingAnchor.constraint(equalTo: title.leadingAnchor),
- ctaButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
- ctaButton.topAnchor.constraint(equalTo: plansStack.bottomAnchor, constant: 20),
- ctaButton.heightAnchor.constraint(equalToConstant: 48),
- footer.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
- footer.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -20),
- ])
- return panel
- }
- private func makeFooter() -> NSView {
- let container = NSView()
- container.translatesAutoresizingMaskIntoConstraints = false
- let restoreLink = PaywallFooterLink(title: "Restore Purchases")
- restoreLink.target = self
- restoreLink.action = #selector(restoreTapped)
- let privacyLink = PaywallFooterLink(title: "Privacy Policy")
- let termsLink = PaywallFooterLink(title: "Terms of Service")
- let dot1 = makeFooterDot()
- let dot2 = makeFooterDot()
- let stack = NSStackView(views: [restoreLink, dot1, privacyLink, dot2, termsLink])
- stack.orientation = .horizontal
- stack.spacing = 6
- stack.alignment = .centerY
- stack.translatesAutoresizingMaskIntoConstraints = false
- container.addSubview(stack)
- NSLayoutConstraint.activate([
- stack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
- stack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
- stack.topAnchor.constraint(equalTo: container.topAnchor),
- stack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
- ])
- return container
- }
- private func makeFooterDot() -> NSView {
- let dot = NSView()
- dot.translatesAutoresizingMaskIntoConstraints = false
- dot.wantsLayer = true
- dot.layer?.backgroundColor = AppTheme.textSecondary.cgColor
- dot.layer?.cornerRadius = 1.5
- NSLayoutConstraint.activate([
- dot.widthAnchor.constraint(equalToConstant: 3),
- dot.heightAnchor.constraint(equalToConstant: 3),
- ])
- return dot
- }
- private func selectPlan(_ plan: PaywallPlan) {
- selectedPlan = plan
- for (key, card) in planCards {
- card.isChosen = key == plan
- }
- ctaButton.title = plan.ctaTitle
- }
- @objc private func purchaseTapped() {
- onPurchase?(selectedPlan)
- }
- @objc private func restoreTapped() {
- onRestore?()
- }
- }
- // MARK: - Overlay Presenter
- final class PaywallOverlayView: NSView {
- var onDismiss: (() -> Void)?
- private let paywallView: PaywallView
- private let blurView = NSVisualEffectView()
- private let backdrop = NSView()
- init() {
- paywallView = PaywallView()
- super.init(frame: .zero)
- translatesAutoresizingMaskIntoConstraints = false
- setup()
- }
- @available(*, unavailable)
- required init?(coder: NSCoder) { nil }
- private func setup() {
- blurView.translatesAutoresizingMaskIntoConstraints = false
- blurView.material = .underWindowBackground
- blurView.blendingMode = .withinWindow
- blurView.state = .active
- backdrop.translatesAutoresizingMaskIntoConstraints = false
- backdrop.wantsLayer = true
- backdrop.layer?.backgroundColor = NSColor(calibratedWhite: 0.15, alpha: 0.22).cgColor
- let pattern = WavePatternView()
- pattern.translatesAutoresizingMaskIntoConstraints = false
- pattern.alphaValue = 0.35
- paywallView.translatesAutoresizingMaskIntoConstraints = false
- paywallView.onClose = { [weak self] in self?.dismiss() }
- paywallView.onPurchase = { plan in
- NSLog("Purchase tapped: \(plan.title)")
- }
- paywallView.onRestore = {
- NSLog("Restore purchases tapped")
- }
- addSubview(blurView)
- addSubview(backdrop)
- backdrop.addSubview(pattern)
- addSubview(paywallView)
- NSLayoutConstraint.activate([
- blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
- blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
- blurView.topAnchor.constraint(equalTo: topAnchor),
- blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
- backdrop.leadingAnchor.constraint(equalTo: leadingAnchor),
- backdrop.trailingAnchor.constraint(equalTo: trailingAnchor),
- backdrop.topAnchor.constraint(equalTo: topAnchor),
- backdrop.bottomAnchor.constraint(equalTo: bottomAnchor),
- pattern.leadingAnchor.constraint(equalTo: backdrop.leadingAnchor),
- pattern.trailingAnchor.constraint(equalTo: backdrop.trailingAnchor),
- pattern.topAnchor.constraint(equalTo: backdrop.topAnchor),
- pattern.bottomAnchor.constraint(equalTo: backdrop.bottomAnchor),
- paywallView.leadingAnchor.constraint(equalTo: leadingAnchor),
- paywallView.trailingAnchor.constraint(equalTo: trailingAnchor),
- paywallView.topAnchor.constraint(equalTo: topAnchor),
- paywallView.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
- }
- func present(in parent: NSView) {
- guard superview == nil else { return }
- if let window = parent.window,
- let windowFrameView = window.contentView?.superview {
- windowFrameView.addSubview(self)
- NSLayoutConstraint.activate([
- leadingAnchor.constraint(equalTo: windowFrameView.leadingAnchor),
- trailingAnchor.constraint(equalTo: windowFrameView.trailingAnchor),
- topAnchor.constraint(equalTo: windowFrameView.topAnchor),
- bottomAnchor.constraint(equalTo: windowFrameView.bottomAnchor),
- ])
- } else {
- parent.addSubview(self)
- NSLayoutConstraint.activate([
- leadingAnchor.constraint(equalTo: parent.leadingAnchor),
- trailingAnchor.constraint(equalTo: parent.trailingAnchor),
- topAnchor.constraint(equalTo: parent.topAnchor),
- bottomAnchor.constraint(equalTo: parent.bottomAnchor),
- ])
- }
- alphaValue = 0
- NSAnimationContext.runAnimationGroup { context in
- context.duration = 0.2
- animator().alphaValue = 1
- }
- }
- func dismiss() {
- NSAnimationContext.runAnimationGroup({ context in
- context.duration = 0.15
- animator().alphaValue = 0
- }, completionHandler: { [weak self] in
- self?.removeFromSuperview()
- self?.onDismiss?()
- })
- }
- }
|