From 91971e034b671a863c0bbaeb2aadca9d83e6bf5c Mon Sep 17 00:00:00 2001 From: Nikita Korobeinikov Date: Wed, 16 Apr 2025 13:39:26 +0400 Subject: [PATCH 1/2] add ability to clear playbook --- SurfPlaybook/Playbook/Core/Playbook.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SurfPlaybook/Playbook/Core/Playbook.swift b/SurfPlaybook/Playbook/Core/Playbook.swift index 2bf2980..9cb4475 100644 --- a/SurfPlaybook/Playbook/Core/Playbook.swift +++ b/SurfPlaybook/Playbook/Core/Playbook.swift @@ -81,4 +81,14 @@ public final class Playbook { return self } + /// Очищает все страницы и координаторы + /// - Note: Применимо, если вы встраиваете Playbook прямо в приложение. + /// Например, в Debug экран. + public func clear() { + chapters.removeAll() + uiKitPages.removeAll() + flowCoordinators.removeAll() + coordinator.removeAllDependencies() + } + } From 509f444e9ef3b8648e833efd3b43f33da821ae77 Mon Sep 17 00:00:00 2001 From: Nikita Korobeinikov Date: Wed, 16 Apr 2025 15:28:03 +0400 Subject: [PATCH 2/2] make all snapshots async --- .../Core/Containers/SUIViewContainer.swift | 6 ++ SurfPlaybook/Playbook/Core/Playbook.swift | 1 + SurfPlaybook/Playbook/Core/PlaybookPage.swift | 2 +- .../Core/PlaybookSnapshotsCache.swift | 65 +++++++++++++++++++ .../Core/Protocols/PlaybookContainer.swift | 11 ++-- .../PageCell/PageCollectionViewCell.swift | 50 ++++++++++---- 6 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 SurfPlaybook/Playbook/Core/PlaybookSnapshotsCache.swift diff --git a/SurfPlaybook/Playbook/Core/Containers/SUIViewContainer.swift b/SurfPlaybook/Playbook/Core/Containers/SUIViewContainer.swift index 646c359..25d155a 100644 --- a/SurfPlaybook/Playbook/Core/Containers/SUIViewContainer.swift +++ b/SurfPlaybook/Playbook/Core/Containers/SUIViewContainer.swift @@ -90,6 +90,12 @@ extension SUIViewContainer: PlaybookContainer { return self } + public func makeSnapshot(with completion: @escaping (UIImage?) -> Void) { + DispatchQueue.main.async { [completion] in + completion(self.makeSnapshot()) + } + } + public func makeSnapshot() -> UIImage? { if #available(iOS 16, *) { ImageRenderer(content: viewFactory().padding(12)).uiImage diff --git a/SurfPlaybook/Playbook/Core/Playbook.swift b/SurfPlaybook/Playbook/Core/Playbook.swift index 9cb4475..59d90e3 100644 --- a/SurfPlaybook/Playbook/Core/Playbook.swift +++ b/SurfPlaybook/Playbook/Core/Playbook.swift @@ -53,6 +53,7 @@ public final class Playbook { /// Каждой главе будет соответствовать карусель на главном экране со страницами из этой главы. @discardableResult public func add(chapter: PlaybookChapter) -> Playbook { + PlaybookSnapshotsCache.shared.add(pages: chapter.pages) chapters.append(chapter) return self } diff --git a/SurfPlaybook/Playbook/Core/PlaybookPage.swift b/SurfPlaybook/Playbook/Core/PlaybookPage.swift index 1bcdab2..3c7770e 100644 --- a/SurfPlaybook/Playbook/Core/PlaybookPage.swift +++ b/SurfPlaybook/Playbook/Core/PlaybookPage.swift @@ -37,7 +37,7 @@ public struct PlaybookPage { // и при необходимости просить контроллер let config: PlaybookContainerProvider var snapshot: UIImage? { - return config(nil).makeSnapshot() + return PlaybookSnapshotsCache.shared.get(page: self) } /// Предпочтительная конфигурация. diff --git a/SurfPlaybook/Playbook/Core/PlaybookSnapshotsCache.swift b/SurfPlaybook/Playbook/Core/PlaybookSnapshotsCache.swift new file mode 100644 index 0000000..dbb1a7e --- /dev/null +++ b/SurfPlaybook/Playbook/Core/PlaybookSnapshotsCache.swift @@ -0,0 +1,65 @@ +// +// PlaybookSnapshotsCache.swift +// Pods +// +// Created by Nikita Korobeinikov on 16.04.2025. +// + +import UIKit +import Foundation + +final class PlaybookSnapshotsCache { + + // MARK: - Snared + + static var shared = PlaybookSnapshotsCache() + + static let didUpdateNotification = Notification.Name("playbook-snapshots-updated") + + /// Поток для синхронизации изменения кэша + private static let queue: DispatchQueue = .init(label: "PlaybookSnapshotsCache") + + // MARK: - Private Properties + + /// Кэш снапшотов + private var snapshots: [String: UIImage?] = [:] { + didSet { + NotificationCenter.default.post( + name: Self.didUpdateNotification, + object: nil + ) + } + } + + // MARK: - Internal Methods + + func add(pages: [PlaybookPage]) { + for page in pages { + add(page: page) + } + } + + func add(page: PlaybookPage) { + page.config(nil).makeSnapshot { snapshot in + Self.queue.sync { [weak self] in + self?.snapshots[page.snapshotKey] = snapshot + } + + } + } + + func get(page: PlaybookPage) -> UIImage? { + snapshots[page.snapshotKey] ?? .init() + } + +} + +// MARK: - Private Extensions + +private extension PlaybookPage { + + var snapshotKey: String { + "\(name)-\(description)" + } + +} diff --git a/SurfPlaybook/Playbook/Core/Protocols/PlaybookContainer.swift b/SurfPlaybook/Playbook/Core/Protocols/PlaybookContainer.swift index 6242445..70ff222 100644 --- a/SurfPlaybook/Playbook/Core/Protocols/PlaybookContainer.swift +++ b/SurfPlaybook/Playbook/Core/Protocols/PlaybookContainer.swift @@ -5,6 +5,7 @@ // Created by Nikita Korobeinikov on 24.01.2025. // +import Foundation import UIKit public typealias PlaybookContainerProvider = (UIViewController?) -> PlaybookContainer @@ -20,7 +21,7 @@ public protocol PlaybookContainer where Self: UIView { func loadView() -> UIView /// Делает снимок вьюшки для отображения превьюшек /// - Note: Перед снимком вызывает `loadView` - func makeSnapshot() -> UIImage? + func makeSnapshot(with completion: @escaping (UIImage?) -> Void) } @@ -28,9 +29,11 @@ public protocol PlaybookContainer where Self: UIView { public extension PlaybookContainer { - func makeSnapshot() -> UIImage? { - loadView().layoutIfNeeded() - return snapshot() + func makeSnapshot(with completion: @escaping (UIImage?) -> Void) { + DispatchQueue.main.async { [completion] in + self.loadView().layoutIfNeeded() + completion(self.snapshot()) + } } } diff --git a/SurfPlaybook/Playbook/Flows/Main/Main/View/Adapter/ChapterCell/PageCell/PageCollectionViewCell.swift b/SurfPlaybook/Playbook/Flows/Main/Main/View/Adapter/ChapterCell/PageCell/PageCollectionViewCell.swift index 37e53cb..002b3e6 100644 --- a/SurfPlaybook/Playbook/Flows/Main/Main/View/Adapter/ChapterCell/PageCell/PageCollectionViewCell.swift +++ b/SurfPlaybook/Playbook/Flows/Main/Main/View/Adapter/ChapterCell/PageCell/PageCollectionViewCell.swift @@ -17,6 +17,10 @@ final class PageCollectionViewCell: UICollectionViewCell { @IBOutlet private weak var snapshotImageView: UIImageView! @IBOutlet private weak var includePresetsImageView: UIImageView! + // MARK: - Private Properties + + private var model: PageModel? + // MARK: - UICollectionView override func awakeFromNib() { @@ -29,6 +33,10 @@ final class PageCollectionViewCell: UICollectionViewCell { configureShadow() } + deinit { + NotificationCenter.default.removeObserver(self) + } + override var isHighlighted: Bool { didSet { UIView.animate(withDuration: AnimationTime.tiny, delay: 0, options: .allowUserInteraction) { @@ -50,19 +58,16 @@ final class PageCollectionViewCell: UICollectionViewCell { titleLabel.apply(style: .textRegular14Black) includePresetsImageView.isHidden = model.playbookPage.presets.count <= 1 - guard let image = model.playbookPage.snapshot else { - snapshotImageView.image = nil - return - } + self.model = model - if - image.size.width > snapshotImageView.bounds.size.width - || image.size.height > snapshotImageView.bounds.size.height { - snapshotImageView.contentMode = .scaleAspectFit - } else { - snapshotImageView.contentMode = .center - } - snapshotImageView.image = image + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver( + self, + selector: #selector(tryUpdateImage), + name: PlaybookSnapshotsCache.didUpdateNotification, + object: nil + ) + tryUpdateImage() } } @@ -92,4 +97,25 @@ private extension PageCollectionViewCell { containerView.layer.shadowOffset = .init(width: 0, height: 2) } + func setImage(_ image: UIImage?) { + guard let image else { + snapshotImageView.image = nil + return + } + + if + image.size.width > snapshotImageView.bounds.size.width + || image.size.height > snapshotImageView.bounds.size.height { + snapshotImageView.contentMode = .scaleAspectFit + } else { + snapshotImageView.contentMode = .center + } + snapshotImageView.image = image + } + + @objc + func tryUpdateImage() { + setImage(model?.playbookPage.snapshot) + } + }