diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 10cd6690..49465a9f 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -673,13 +673,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "$(SRCROOT)/Sources/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.messagekit.ChatExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -688,6 +688,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "$(SRCROOT)/Sources/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -696,7 +697,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -705,6 +705,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -712,7 +713,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatExample.app/ChatExample"; }; name = Debug; @@ -722,6 +722,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -731,7 +732,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatExample.app/ChatExample"; }; name = Release; @@ -740,6 +740,7 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = UITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -747,7 +748,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; TEST_TARGET_NAME = ChatExample; }; name = Debug; @@ -756,6 +756,7 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = UITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -765,7 +766,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; TEST_TARGET_NAME = ChatExample; }; name = Release; @@ -817,7 +817,7 @@ repositoryURL = "https://github.com/onevcat/Kingfisher"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.15.8; + minimumVersion = 8.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Example/Sources/AppDelegate.swift b/Example/Sources/AppDelegate.swift index 3cb9f9d4..604e3eb4 100644 --- a/Example/Sources/AppDelegate.swift +++ b/Example/Sources/AppDelegate.swift @@ -22,7 +22,7 @@ import UIKit -@UIApplicationMain +@main final internal class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? @@ -35,7 +35,7 @@ final internal class AppDelegate: UIResponder, UIApplicationDelegate { UIViewController() ] : [masterViewController] - splitViewController.preferredDisplayMode = .allVisible + splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.oneBesideSecondary masterViewController.navigationItem.largeTitleDisplayMode = .never window = UIWindow(frame: UIScreen.main.bounds) diff --git a/Example/Sources/AudioController/BasicAudioController.swift b/Example/Sources/AudioController/BasicAudioController.swift index 8bdd84d8..9f239d3d 100644 --- a/Example/Sources/AudioController/BasicAudioController.swift +++ b/Example/Sources/AudioController/BasicAudioController.swift @@ -42,7 +42,8 @@ public enum PlayerState { /// The `BasicAudioController` update UI for current audio cell that is playing a sound /// and also creates and manage an `AVAudioPlayer` states, play, pause and stop. -open class BasicAudioController: NSObject, AVAudioPlayerDelegate { +@MainActor +open class BasicAudioController: NSObject, @preconcurrency AVAudioPlayerDelegate { // MARK: Lifecycle // MARK: - Init Methods diff --git a/Example/Sources/Models/MockSocket.swift b/Example/Sources/Models/MockSocket.swift index d48cacd6..41c9f1b1 100644 --- a/Example/Sources/Models/MockSocket.swift +++ b/Example/Sources/Models/MockSocket.swift @@ -23,6 +23,7 @@ import MessageKit import UIKit +@MainActor final class MockSocket { // MARK: Lifecycle diff --git a/Package.swift b/Package.swift index b0e0f674..ca0bcbd5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.0 // MIT License // @@ -24,14 +24,12 @@ import PackageDescription let package = Package( name: "MessageKit", - platforms: [.iOS(.v13)], + platforms: [.iOS(.v14)], products: [ - .library(name: "MessageKit", targets: ["MessageKit"]), - .plugin(name: "LintPlugin", targets: ["LintPlugin"]), - .plugin(name: "SwiftFormatPlugin", targets: ["SwiftFormatPlugin"]), + .library(name: "MessageKit", targets: ["MessageKit"]) ], dependencies: [ - .package(url: "https://github.com/nathantannar4/InputBarAccessoryView", .upToNextMajor(from: "6.5.0")), + .package(url: "https://github.com/nathantannar4/InputBarAccessoryView", .upToNextMajor(from: "7.0.0")), ], targets: [ // MARK: - MessageKit @@ -44,45 +42,6 @@ let package = Package( swiftSettings: [SwiftSetting.define("IS_SPM")] ), .testTarget(name: "MessageKitTests", dependencies: ["MessageKit"]), - - // MARK: - Plugins - - .binaryTarget( - name: "LintBinary", - url: "https://github.com/realm/SwiftLint/releases/download/0.47.1/SwiftLintBinary-macos.artifactbundle.zip", - checksum: "82ef90b7d76b02e41edd73423687d9cedf0bb9849dcbedad8df3a461e5a7b555" - ), - .plugin( - name: "LintPlugin", - capability: .buildTool(), - dependencies: ["LintBinary"] - ), - .plugin( - name: "LintCommandPlugin", - capability: .command( - intent: .custom( - verb: "lint", - description: "Lint Swift source files" - ) - ), - dependencies: ["LintBinary"] - ), - - .binaryTarget( - name: "swiftformat", - url: "https://github.com/nicklockwood/SwiftFormat/releases/download/0.49.13/swiftformat.artifactbundle.zip", - checksum: "5ce27780dceee8714b15d53141e6dce1a8d626e970eade3c511c9ef1a0c06f40" - ), - .plugin( - name: "SwiftFormatPlugin", - capability: .command( - intent: .sourceCodeFormatting(), - permissions: [ - .writeToPackageDirectory(reason: "Format Swift source files"), - ] - ), - dependencies: ["swiftformat"] - ), ], - swiftLanguageVersions: [.v5] + swiftLanguageModes: [.v6] ) diff --git a/Plugins/LintCommandPlugin/SwiftLintCommandPlugin.swift b/Plugins/LintCommandPlugin/SwiftLintCommandPlugin.swift deleted file mode 100644 index c75bcf17..00000000 --- a/Plugins/LintCommandPlugin/SwiftLintCommandPlugin.swift +++ /dev/null @@ -1,54 +0,0 @@ -// MIT License -// -// Copyright (c) 2017-2022 MessageKit -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import Foundation -import PackagePlugin - -// MARK: - SwiftLintCommandPlugin - -@main -struct SwiftLintCommandPlugin: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments _: [String]) async throws { - let swiftLintTool = try context.tool(named: "swiftlint") - let swiftLintPath = URL(fileURLWithPath: swiftLintTool.path.string) - - let swiftLintArgs = [ - "lint", - "--path", context.package.directory.string, - "--config", context.package.directory.string + "/.swiftlint.yml", - "--strict", - ] - - let task = try Process.run(swiftLintPath, arguments: swiftLintArgs) - task.waitUntilExit() - - if task.terminationStatus == 0 || task.terminationStatus == 2 { - // no-op - } else { - throw CommandError.unknownError(exitCode: task.terminationStatus) - } - } -} - -// MARK: - CommandError - -enum CommandError: Error { - case unknownError(exitCode: Int32) -} diff --git a/Plugins/LintPlugin/SwiftLintPlugin.swift b/Plugins/LintPlugin/SwiftLintPlugin.swift deleted file mode 100644 index e88b10ab..00000000 --- a/Plugins/LintPlugin/SwiftLintPlugin.swift +++ /dev/null @@ -1,41 +0,0 @@ -// MIT License -// -// Copyright (c) 2017-2022 MessageKit -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import PackagePlugin - -@main -struct LintPlugin: BuildToolPlugin { - func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { - [ - .buildCommand( - displayName: "Linting \(target.name)", - executable: try context.tool(named: "swiftlint").path, - arguments: [ - "lint", - "--in-process-sourcekit", - "--path", - target.directory.string, - "--config", - "\(context.package.directory.string)/.swiftlint.yml", - ], - environment: [:]), - ] - } -} diff --git a/Plugins/SwiftFormatPlugin/SwiftFormatPlugin.swift b/Plugins/SwiftFormatPlugin/SwiftFormatPlugin.swift deleted file mode 100644 index 34b1d70d..00000000 --- a/Plugins/SwiftFormatPlugin/SwiftFormatPlugin.swift +++ /dev/null @@ -1,61 +0,0 @@ -// MIT License -// -// Copyright (c) 2017-2022 MessageKit -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import Foundation -import PackagePlugin - -// MARK: - SwiftFormatPlugin - -@main -struct SwiftFormatPlugin: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - var argumentExtractor = ArgumentExtractor(arguments) - let files = argumentExtractor.extractOption(named: "file") - - let process = Process() - process.launchPath = try context.tool(named: "swiftformat").path.string - process.arguments = [ - files.first!, - "--swiftversion", - "5.6", - "--cache", - context.pluginWorkDirectory.string + "/swiftformat.cache", - ] - - try process.run() - process.waitUntilExit() - - switch process.terminationStatus { - case EXIT_SUCCESS: - break - case EXIT_FAILURE: - throw CommandError.lintFailure - default: - throw CommandError.unknownError(exitCode: process.terminationStatus) - } - } -} - -// MARK: - CommandError - -enum CommandError: Error { - case lintFailure - case unknownError(exitCode: Int32) -} diff --git a/Sources/Controllers/MessagesViewController+Menu.swift b/Sources/Controllers/MessagesViewController+Menu.swift index d454d3fc..7aa07a36 100644 --- a/Sources/Controllers/MessagesViewController+Menu.swift +++ b/Sources/Controllers/MessagesViewController+Menu.swift @@ -36,10 +36,6 @@ extension MessagesViewController { object: nil) } - internal func removeMenuControllerObservers() { - NotificationCenter.default.removeObserver(self, name: UIMenuController.willShowMenuNotification, object: nil) - } - // MARK: Private // MARK: - Helpers diff --git a/Sources/Controllers/MessagesViewController+State.swift b/Sources/Controllers/MessagesViewController+State.swift index d72c3d18..62d2d9cd 100644 --- a/Sources/Controllers/MessagesViewController+State.swift +++ b/Sources/Controllers/MessagesViewController+State.swift @@ -26,17 +26,18 @@ import InputBarAccessoryView import UIKit extension MessagesViewController { - final class State { - /// Pan gesture for display the date of message by swiping left. - var panGesture: UIPanGestureRecognizer? - var maintainPositionOnInputBarHeightChanged = false - var scrollsToLastItemOnKeyboardBeginsEditing = false + @MainActor + final class State { + /// Pan gesture for display the date of message by swiping left. + var panGesture: UIPanGestureRecognizer? + var maintainPositionOnInputBarHeightChanged = false + var scrollsToLastItemOnKeyboardBeginsEditing = false - let inputContainerView: MessagesInputContainerView = .init() - @Published var inputBarType: MessageInputBarKind = .messageInputBar - let keyboardManager = KeyboardManager() - var disposeBag: Set = .init() - } + let inputContainerView: MessagesInputContainerView = .init() + @Published var inputBarType: MessageInputBarKind = .messageInputBar + let keyboardManager = KeyboardManager() + var disposeBag: Set = .init() + } // MARK: - Getters diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift index 1046517d..41965d5a 100644 --- a/Sources/Controllers/MessagesViewController.swift +++ b/Sources/Controllers/MessagesViewController.swift @@ -31,8 +31,8 @@ open class MessagesViewController: UIViewController, UICollectionViewDelegateFlo // MARK: Lifecycle deinit { - removeMenuControllerObservers() - clearMemoryCache() + NotificationCenter.default.removeObserver(self, name: UIMenuController.willShowMenuNotification, object: nil) + MessageStyle.bubbleImageCache.removeAllObjects() } // MARK: Open diff --git a/Sources/Extensions/Bundle+Extensions.swift b/Sources/Extensions/Bundle+Extensions.swift index bc88f309..aa4ca7d4 100644 --- a/Sources/Extensions/Bundle+Extensions.swift +++ b/Sources/Extensions/Bundle+Extensions.swift @@ -24,9 +24,9 @@ import Foundation extension Bundle { #if IS_SPM - internal static var messageKitAssetBundle = Bundle.module + nonisolated internal static let messageKitAssetBundle = Bundle.module #else - internal static var messageKitAssetBundle: Bundle { + nonisolated internal static var messageKitAssetBundle: Bundle { Bundle(for: MessagesViewController.self) } #endif diff --git a/Sources/Extensions/UIColor+Extensions.swift b/Sources/Extensions/UIColor+Extensions.swift index b40233ce..e5df7ad8 100644 --- a/Sources/Extensions/UIColor+Extensions.swift +++ b/Sources/Extensions/UIColor+Extensions.swift @@ -23,6 +23,7 @@ import Foundation import UIKit +@MainActor extension UIColor { // MARK: Internal diff --git a/Sources/Layout/CellSizeCalculator.swift b/Sources/Layout/CellSizeCalculator.swift index 6c153ceb..85ffbffe 100644 --- a/Sources/Layout/CellSizeCalculator.swift +++ b/Sources/Layout/CellSizeCalculator.swift @@ -24,6 +24,7 @@ import UIKit /// An object is responsible for /// sizing and configuring cells for given `IndexPath`s. +@MainActor open class CellSizeCalculator { // MARK: Lifecycle diff --git a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift index d6d69e12..ff8fefad 100644 --- a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift +++ b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift @@ -23,7 +23,7 @@ import UIKit /// The layout attributes used by a `MessageCollectionViewCell` to layout its subviews. -open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes { +open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes { // MARK: Open // MARK: - Methods @@ -55,36 +55,6 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu // swiftlint:enable force_cast } - open override func isEqual(_ object: Any?) -> Bool { - // MARK: - LEAVE this as is - if let attributes = object as? MessagesCollectionViewLayoutAttributes { - return super.isEqual(object) && attributes.avatarSize == avatarSize - && attributes.avatarPosition == avatarPosition - && attributes.avatarLeadingTrailingPadding == avatarLeadingTrailingPadding - && attributes.messageContainerSize == messageContainerSize - && attributes.messageContainerPadding == messageContainerPadding - && attributes.messageLabelFont == messageLabelFont - && attributes.messageLabelInsets == messageLabelInsets - && attributes.cellTopLabelAlignment == cellTopLabelAlignment - && attributes.cellTopLabelSize == cellTopLabelSize - && attributes.cellBottomLabelAlignment == cellBottomLabelAlignment - && attributes.cellBottomLabelSize == cellBottomLabelSize - && attributes.messageTimeLabelSize == messageTimeLabelSize - && attributes.messageTopLabelAlignment == messageTopLabelAlignment - && attributes.messageTopLabelSize == messageTopLabelSize - && attributes.messageBottomLabelAlignment == messageBottomLabelAlignment - && attributes.messageBottomLabelSize == messageBottomLabelSize - && attributes.accessoryViewSize == accessoryViewSize - && attributes.accessoryViewPadding == accessoryViewPadding - && attributes.accessoryViewPosition == accessoryViewPosition - && attributes.linkPreviewFonts == linkPreviewFonts - } else { - return false - } - } - - // MARK: Public - // MARK: - Properties public var avatarSize: CGSize = .zero diff --git a/Sources/Models/AccessoryPosition.swift b/Sources/Models/AccessoryPosition.swift index 3fd69bb3..59c09c5c 100644 --- a/Sources/Models/AccessoryPosition.swift +++ b/Sources/Models/AccessoryPosition.swift @@ -24,7 +24,7 @@ import Foundation /// Used to determine the `Horizontal` and `Vertical` position of /// an `AccessoryView` in a `MessageCollectionViewCell`. -public enum AccessoryPosition { +public enum AccessoryPosition: Equatable { /// Aligns the `AccessoryView`'s top edge to the cell's top edge. case cellTop diff --git a/Sources/Models/DetectorType.swift b/Sources/Models/DetectorType.swift index d2dd83e9..7fd0cb22 100644 --- a/Sources/Models/DetectorType.swift +++ b/Sources/Models/DetectorType.swift @@ -22,7 +22,7 @@ import Foundation -public enum DetectorType: Hashable { +public enum DetectorType: Hashable, Sendable { case address case date case phoneNumber @@ -33,8 +33,8 @@ public enum DetectorType: Hashable { // MARK: Public // swiftlint:disable force_try - public static var hashtag = DetectorType.custom(try! NSRegularExpression(pattern: "#[a-zA-Z0-9]{4,}", options: [])) - public static var mention = DetectorType.custom(try! NSRegularExpression(pattern: "@[a-zA-Z0-9]{4,}", options: [])) + public static let hashtag = DetectorType.custom(try! NSRegularExpression(pattern: "#[a-zA-Z0-9]{4,}", options: [])) + public static let mention = DetectorType.custom(try! NSRegularExpression(pattern: "@[a-zA-Z0-9]{4,}", options: [])) /// Simply check if the detector type is a .custom public var isCustom: Bool { diff --git a/Sources/Models/LocationMessageSnapshotOptions.swift b/Sources/Models/LocationMessageSnapshotOptions.swift index bed66843..792a0731 100644 --- a/Sources/Models/LocationMessageSnapshotOptions.swift +++ b/Sources/Models/LocationMessageSnapshotOptions.swift @@ -23,6 +23,7 @@ import MapKit /// An object grouping the settings used by the `MKMapSnapshotter` through the `LocationMessageDisplayDelegate`. +@MainActor public struct LocationMessageSnapshotOptions { // MARK: Lifecycle diff --git a/Sources/Models/MessageKitDateFormatter.swift b/Sources/Models/MessageKitDateFormatter.swift index 7d39a9fa..c3636034 100644 --- a/Sources/Models/MessageKitDateFormatter.swift +++ b/Sources/Models/MessageKitDateFormatter.swift @@ -22,7 +22,7 @@ import Foundation -open class MessageKitDateFormatter { +open class MessageKitDateFormatter: @unchecked Sendable { // MARK: Lifecycle // MARK: - Initializer diff --git a/Sources/Models/MessageStyle.swift b/Sources/Models/MessageStyle.swift index e60b9f97..ed1dbeba 100644 --- a/Sources/Models/MessageStyle.swift +++ b/Sources/Models/MessageStyle.swift @@ -111,13 +111,13 @@ public enum MessageStyle { return strechAndCache(image: image) } - // MARK: Internal + // MARK: Internal - internal static let bubbleImageCache: NSCache = { - let cache = NSCache() - cache.name = "com.messagekit.MessageKit.bubbleImageCache" - return cache - }() + nonisolated(unsafe) internal static let bubbleImageCache: NSCache = { + let cache = NSCache() + cache.name = "com.messagekit.MessageKit.bubbleImageCache" + return cache + }() // MARK: Private diff --git a/Sources/Protocols/MessagesDataSource.swift b/Sources/Protocols/MessagesDataSource.swift index 7729d898..831d1cf2 100644 --- a/Sources/Protocols/MessagesDataSource.swift +++ b/Sources/Protocols/MessagesDataSource.swift @@ -26,6 +26,7 @@ import UIKit /// An object that adopts the `MessagesDataSource` protocol is responsible for providing /// the data required by a `MessagesCollectionView`. +@MainActor public protocol MessagesDataSource: AnyObject { /// The `SenderType` of new messages in the `MessagesCollectionView`. var currentSender: SenderType { get } diff --git a/Sources/Protocols/MessagesDisplayDelegate.swift b/Sources/Protocols/MessagesDisplayDelegate.swift index 2548fde8..81a5d672 100644 --- a/Sources/Protocols/MessagesDisplayDelegate.swift +++ b/Sources/Protocols/MessagesDisplayDelegate.swift @@ -27,6 +27,7 @@ import UIKit // MARK: - MessagesDisplayDelegate /// A protocol used by the `MessagesViewController` to customize the appearance of a `MessageContentCell`. +@MainActor public protocol MessagesDisplayDelegate: AnyObject { // MARK: - All Messages diff --git a/Sources/Protocols/MessagesLayoutDelegate.swift b/Sources/Protocols/MessagesLayoutDelegate.swift index b6fafe1c..015c45c3 100644 --- a/Sources/Protocols/MessagesLayoutDelegate.swift +++ b/Sources/Protocols/MessagesLayoutDelegate.swift @@ -27,6 +27,7 @@ import UIKit /// A protocol used by the `MessagesCollectionViewFlowLayout` object to determine /// the size and layout of a `MessageCollectionViewCell` and its contents. +@MainActor public protocol MessagesLayoutDelegate: AnyObject { /// Specifies the size to use for a header view. /// diff --git a/Sources/Views/TypingIndicator.swift b/Sources/Views/TypingIndicator.swift index 208ce337..f0eb73d3 100644 --- a/Sources/Views/TypingIndicator.swift +++ b/Sources/Views/TypingIndicator.swift @@ -87,23 +87,23 @@ open class TypingIndicator: UIView { /// Sets the state of the `TypingIndicator` to animating and applies animation layers open func startAnimating() { - defer { isAnimating = true } - guard !isAnimating else { return } - var delay: TimeInterval = 0 - for dot in dots { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in - guard let self = self else { return } - if self.isBounceEnabled { - dot.layer.add(self.initialOffsetAnimationLayer, forKey: AnimationKeys.offset) - let bounceLayer = self.bounceAnimationLayer - bounceLayer.timeOffset = delay + 0.33 - dot.layer.add(bounceLayer, forKey: AnimationKeys.bounce) - } - if self.isFadeEnabled { - dot.layer.add(self.opacityAnimationLayer, forKey: AnimationKeys.opacity) - } - } - delay += 0.33 + defer { isAnimating = true } + guard !isAnimating else { return } + var delay: TimeInterval = 0 + for dot in dots { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + guard let self = self else { return } + if self.isBounceEnabled { + dot.layer.add(self.initialOffsetAnimationLayer, forKey: AnimationKeys.offset) + let bounceLayer = self.bounceAnimationLayer + bounceLayer.timeOffset = delay + 0.33 + dot.layer.add(bounceLayer, forKey: AnimationKeys.bounce) + } + if self.isFadeEnabled { + dot.layer.add(self.opacityAnimationLayer, forKey: AnimationKeys.opacity) + } + } + delay += 0.33 } }