Я пытаюсь создать слайд-анимацию для окна в macOS с помощью AppKit, но столкнулся с некоторыми проблемами с поведением анимации.
Анимация путем изменения координаты X: когда я анимирую координату X окна с «-width» на «0», окно сначала появляется на левом экране, а затем перемещается вправо.
Анимация путем изменения ширины. Если я попытаюсь анимировать, изменив ширину окна, 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 или SwiftUI. У меня возникли проблемы с выяснением того, как анимировать часть «Окно».
Вместо анимации слайда всего окна я бы рекомендовал использовать чистое прозрачное окно без полей и анимацию в подпредставлении в представлении содержимого с использованием ограничений макета.
Пример макета раскадровки:
Ограничения:
Таким образом, изначально ведущее ограничение Slide In View имеет отрицательное значение ширины (-250
). Это вытолкнет его из видимого окна и полностью из поля зрения. Поскольку любой контент просмотра за пределами их окна не будет виден, это позволит избежать проблемы, с которой вы столкнулись, когда он отображался на втором экране слева от того, на который вы пытались перейти.
Чтобы сделать окно прозрачным/без полей, используйте следующие настройки:
.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?
Я понял это. Еще раз спасибо.
Альтернативой является использование
SwiftUI
со всеми хорошими функциями анимации, которые должны быть совместимы сAppKit
. См. также: Интеграция AppKit