Как создать слайд-анимацию в macOS с помощью AppKit

Я пытаюсь создать слайд-анимацию для окна в macOS с помощью AppKit, но столкнулся с некоторыми проблемами с поведением анимации.

  1. Анимация путем изменения координаты X: когда я анимирую координату X окна с «-width» на «0», окно сначала появляется на левом экране, а затем перемещается вправо.

    Анимация путем изменения координаты X

  2. Анимация путем изменения ширины. Если я попытаюсь анимировать, изменив ширину окна, contentView будет обрезан во время анимации.

    Анимация путем изменения ширины

Вот пример кода, который я использую для реализации анимации:

let contentView = ContentView(viewModel: viewModel)
let dockWindow = NSWindow(
    contentRect: NSRect(x: 0, y: 0, width: 250, height: 900),
    styleMask: [.nonactivatingPanel],
    backing: .buffered,
    defer: false
)
if let focusedScreen = NSScreen.main {
                let screenHeight = focusedScreen.visibleFrame.height
                let targetY = (screenHeight - dockWindow.frame.height) / 2 + focusedScreen.visibleFrame.origin.y
                let targetRect = NSRect(
                    x: focusedScreen.visibleFrame.minX, 
                    y: targetY, 
                    width: dockWindow.frame.width,
                    height: dockWindow.frame.height
                )
                
                let initialRect = NSRect(x: focusedScreen.visibleFrame.minX,
                                         y: targetY,
                                         width: 0, //animate by change the width of the window
                                         height: dockWindow.frame.height)
                dockWindow.setFrame(initialRect, display: true)
                
                NSAnimationContext.runAnimationGroup { context in
                    context.duration = 0.2
                    context.allowsImplicitAnimation = true
                    dockWindow.setFrame(targetRect, display: true)
                } completionHandler: {
                    self.isAnimating = false
                }
            } 
else {
                isAnimating = false
            }

Я ищу предложения о том, как создать плавный эффект скольжения без упомянутых проблем. Будем очень признательны за любые идеи или альтернативные подходы.

Заранее спасибо за вашу помощь!

Альтернативой является использование SwiftUI со всеми хорошими функциями анимации, которые должны быть совместимы с AppKit. См. также: Интеграция AppKit

workingdog support Ukraine 27.06.2024 04:33

Спасибо за предложение! Я уже использую SwiftUI, но не слишком знаком с AppKit или SwiftUI. У меня возникли проблемы с выяснением того, как анимировать часть «Окно».

0x55aa 28.06.2024 13:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вместо анимации слайда всего окна я бы рекомендовал использовать чистое прозрачное окно без полей и анимацию в подпредставлении в представлении содержимого с использованием ограничений макета.

Пример макета раскадровки:

Ограничения:

Таким образом, изначально ведущее ограничение Slide In View имеет отрицательное значение ширины (-250). Это вытолкнет его из видимого окна и полностью из поля зрения. Поскольку любой контент просмотра за пределами их окна не будет виден, это позволит избежать проблемы, с которой вы столкнулись, когда он отображался на втором экране слева от того, на который вы пытались перейти.

Чтобы сделать окно прозрачным/без полей, используйте следующие настройки:

  • снимите флажок показывать строку заголовка в раскадровке
  • Проверьте полноразмерный просмотр содержимого
  • снимите флажок с тени
  • программно установить для isOpaque значение false
  • программно установить для фона цвет .clear

Когда мы будем готовы вставить анимацию, измените константу ведущего ограничения на 0 (охватывая левую часть окна), а затем анимируйте компоновку представления, чтобы вставить его.

Пример кода для SlideInViewController:

import Cocoa

class SlideInViewController: NSViewController {
    
    @IBOutlet public var leadingConstraint: NSLayoutConstraint?
    @IBOutlet public var slideInView: NSView?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.slideInView?.wantsLayer = true
        self.slideInView?.layer?.backgroundColor = .white
    }
    
    public func slideIn() {
        if let screenFrame = NSScreen.main?.visibleFrame {
            let centeredY = (screenFrame.size.height - self.view.frame.size.height) / 2 + screenFrame.origin.y
            let windowFrame = NSRect(
                x: screenFrame.minX,
                y: centeredY,
                width: self.view.frame.size.width,
                height: self.view.frame.size.height
            )
            self.view.window?.isOpaque = false
            self.view.window?.backgroundColor = .clear
            self.view.window?.setFrame(windowFrame, display: true)
            self.view.window?.orderFront(nil)
            self.leadingConstraint?.constant = 0.0
            NSAnimationContext.runAnimationGroup { context in
                context.allowsImplicitAnimation = true
                context.duration = 2.0
                self.view.layoutSubtreeIfNeeded()
            } completionHandler: {
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
                    self.slideOut()
                }
            }
        }
    }
    
    public func slideOut() {
        if let slideInViewWidth = self.slideInView?.frame.size.width {
            self.leadingConstraint?.constant = -slideInViewWidth
            NSAnimationContext.runAnimationGroup { context in
                context.allowsImplicitAnimation = true
                context.duration = 2.0
                self.view.layoutSubtreeIfNeeded()
            } completionHandler: {
                self.view.window?.orderOut(nil)
            }
            
        }
    }
    
}

Пример сайта вызова в приложении AppDelegateDidFinishLaunching:

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        let slideInViewController = (storyboard.instantiateController(withIdentifier: "slideInWindow") as? NSWindowController)?.contentViewController as? SlideInViewController
        slideInViewController?.slideIn()
    }

Пример этого в действии:

Это блестящая идея! Не могли бы вы также привести пример того, как этого добиться с помощью SwiftUI?

0x55aa 28.06.2024 13:20

Я понял это. Еще раз спасибо.

0x55aa 30.06.2024 09:48

Другие вопросы по теме