forked from shadowfacts/Tusker
Move GIFImageView to TuskerComponents
This commit is contained in:
parent
bb3f353dbc
commit
350e331eb2
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// GIFImageView.swift
|
// GIFImageView.swift
|
||||||
// Tusker
|
// TuskerComponents
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 11/11/21.
|
// Created by Shadowfacts on 11/11/21.
|
||||||
// Copyright © 2021 Shadowfacts. All rights reserved.
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
@ -8,36 +8,36 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class GIFImageView: UIImageView {
|
open class GIFImageView: UIImageView {
|
||||||
|
|
||||||
fileprivate(set) var gifController: GIFController? = nil
|
public fileprivate(set) var gifController: GIFController? = nil
|
||||||
var isAnimatingGIF: Bool { gifController?.state == .playing }
|
public var isAnimatingGIF: Bool { gifController?.state == .playing }
|
||||||
|
|
||||||
/// Detaches the current GIF controller from this view.
|
/// Detaches the current GIF controller from this view.
|
||||||
/// If this view is the GIF controller's only one, it will stop itself.
|
/// If this view is the GIF controller's only one, it will stop itself.
|
||||||
func detachGIFController() {
|
public func detachGIFController() {
|
||||||
gifController?.detach(from: self)
|
gifController?.detach(from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `GIFController` controls the animation of one or more `GIFImageView`s.
|
/// A `GIFController` controls the animation of one or more `GIFImageView`s.
|
||||||
class GIFController {
|
public class GIFController {
|
||||||
|
|
||||||
// GIFImageView strongly holds the controller so that when the last view detaches, the controller is freed
|
// GIFImageView strongly holds the controller so that when the last view detaches, the controller is freed
|
||||||
private var imageViews = WeakArray<GIFImageView>()
|
private var imageViews = WeakArray<GIFImageView>()
|
||||||
|
|
||||||
private(set) var gifData: Data
|
public let gifData: Data
|
||||||
private(set) var state: State = .stopped
|
private(set) var state: State = .stopped
|
||||||
private(set) var lastFrame: (image: UIImage, index: Int)? = nil
|
public private(set) var lastFrame: (image: UIImage, index: Int)? = nil
|
||||||
|
|
||||||
init(gifData: Data) {
|
public init(gifData: Data) {
|
||||||
self.gifData = gifData
|
self.gifData = gifData
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attaches another view to this controller, letting it play back alongside the others.
|
/// Attaches another view to this controller, letting it play back alongside the others.
|
||||||
/// Immediately brings it into sync with the others, setting the last frame if there was one.
|
/// Immediately brings it into sync with the others, setting the last frame if there was one.
|
||||||
func attach(to view: GIFImageView) {
|
public func attach(to view: GIFImageView) {
|
||||||
imageViews.append(view)
|
imageViews.append(view)
|
||||||
view.gifController = self
|
view.gifController = self
|
||||||
|
|
||||||
|
@ -49,13 +49,13 @@ class GIFController {
|
||||||
/// Detaches the given view from this controller.
|
/// Detaches the given view from this controller.
|
||||||
/// If no views attached views remain, the last strong reference to this controller is nilled out
|
/// If no views attached views remain, the last strong reference to this controller is nilled out
|
||||||
/// and image animation will stop at the next CGAnimateImageDataWithBlock callback.
|
/// and image animation will stop at the next CGAnimateImageDataWithBlock callback.
|
||||||
func detach(from view: GIFImageView) {
|
public func detach(from view: GIFImageView) {
|
||||||
// todo: does === work the way i want here
|
// todo: does === work the way i want here
|
||||||
imageViews.removeAll(where: { $0 === view })
|
imageViews.removeAll(where: { $0 === view })
|
||||||
view.gifController = nil
|
view.gifController = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startAnimating() {
|
public func startAnimating() {
|
||||||
guard state.shouldStop else { return }
|
guard state.shouldStop else { return }
|
||||||
|
|
||||||
state = .playing
|
state = .playing
|
||||||
|
@ -74,7 +74,7 @@ class GIFController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopAnimating() {
|
public func stopAnimating() {
|
||||||
guard state == .playing else { return }
|
guard state == .playing else { return }
|
||||||
|
|
||||||
state = .stopping
|
state = .stopping
|
||||||
|
@ -89,3 +89,47 @@ class GIFController {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class WeakHolder<T: AnyObject> {
|
||||||
|
weak var object: T?
|
||||||
|
|
||||||
|
init(_ object: T?) {
|
||||||
|
self.object = object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct WeakArray<Element: AnyObject>: MutableCollection, RangeReplaceableCollection {
|
||||||
|
private var array: [WeakHolder<Element>]
|
||||||
|
|
||||||
|
var startIndex: Int { array.startIndex }
|
||||||
|
var endIndex: Int { array.endIndex }
|
||||||
|
|
||||||
|
init() {
|
||||||
|
array = []
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ elements: [Element]) {
|
||||||
|
array = elements.map { WeakHolder($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ elements: [Element?]) {
|
||||||
|
array = elements.map { WeakHolder($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(position: Int) -> Element? {
|
||||||
|
get {
|
||||||
|
array[position].object
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
array[position] = WeakHolder(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(after i: Int) -> Int {
|
||||||
|
return array.index(after: i)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, Self.Element == C.Element {
|
||||||
|
array.replaceSubrange(subrange, with: newElements.map { WeakHolder($0) })
|
||||||
|
}
|
||||||
|
}
|
|
@ -332,7 +332,6 @@
|
||||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||||
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */; };
|
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */; };
|
||||||
D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A44273D6C5700386A6C /* GIFImageView.swift */; };
|
|
||||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; };
|
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; };
|
||||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; };
|
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; };
|
||||||
D6DD8FFD298495A8002AD3FD /* LogoutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD8FFC298495A8002AD3FD /* LogoutService.swift */; };
|
D6DD8FFD298495A8002AD3FD /* LogoutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD8FFC298495A8002AD3FD /* LogoutService.swift */; };
|
||||||
|
@ -758,7 +757,6 @@
|
||||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||||
D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentImage.swift; sourceTree = "<group>"; };
|
D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentImage.swift; sourceTree = "<group>"; };
|
||||||
D6DD2A44273D6C5700386A6C /* GIFImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GIFImageView.swift; sourceTree = "<group>"; };
|
|
||||||
D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningCopyMode.swift; sourceTree = "<group>"; };
|
D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningCopyMode.swift; sourceTree = "<group>"; };
|
||||||
D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Notification.swift"; sourceTree = "<group>"; };
|
D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Notification.swift"; sourceTree = "<group>"; };
|
||||||
D6DD8FFC298495A8002AD3FD /* LogoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutService.swift; sourceTree = "<group>"; };
|
D6DD8FFC298495A8002AD3FD /* LogoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutService.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1459,7 +1457,6 @@
|
||||||
D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */,
|
D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */,
|
||||||
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
||||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
||||||
D6DD2A44273D6C5700386A6C /* GIFImageView.swift */,
|
|
||||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||||
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
||||||
D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */,
|
D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */,
|
||||||
|
@ -2145,7 +2142,6 @@
|
||||||
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
||||||
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */,
|
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */,
|
||||||
D65B4B6229771A3F00DABDFB /* FetchStatusService.swift in Sources */,
|
D65B4B6229771A3F00DABDFB /* FetchStatusService.swift in Sources */,
|
||||||
D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */,
|
|
||||||
D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */,
|
D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */,
|
||||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
class AttachmentPreviewViewController: UIViewController {
|
class AttachmentPreviewViewController: UIViewController {
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Photos
|
import Photos
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
struct ComposeAttachmentImage: View {
|
struct ComposeAttachmentImage: View {
|
||||||
let attachment: CompositionAttachment
|
let attachment: CompositionAttachment
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
@preconcurrency import AVFoundation
|
@preconcurrency import AVFoundation
|
||||||
@preconcurrency import VisionKit
|
@preconcurrency import VisionKit
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
protocol LargeImageContentView: UIView {
|
protocol LargeImageContentView: UIView {
|
||||||
var animationImage: UIImage? { get }
|
var animationImage: UIImage? { get }
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableViewController {
|
class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableViewController {
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
protocol LargeImageAnimatableViewController: UIViewController {
|
protocol LargeImageAnimatableViewController: UIViewController {
|
||||||
var animationSourceView: UIImageView? { get }
|
var animationSourceView: UIImageView? { get }
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import TuskerComponents
|
||||||
|
|
||||||
protocol AttachmentViewDelegate: AnyObject {
|
protocol AttachmentViewDelegate: AnyObject {
|
||||||
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController?
|
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController?
|
||||||
|
|
Loading…
Reference in New Issue