diff --git a/ChatLayout/Classes/Core/CollectionViewChatLayout.swift b/ChatLayout/Classes/Core/CollectionViewChatLayout.swift index 4523fba..5d0d921 100644 --- a/ChatLayout/Classes/Core/CollectionViewChatLayout.swift +++ b/ChatLayout/Classes/Core/CollectionViewChatLayout.swift @@ -20,28 +20,28 @@ import AppKit import UIKit #endif -/// A collection view layout designed to display items in a grid similar to `UITableView`, while aligning them to the -/// leading or trailing edge of the `UICollectionView`. This layout facilitates chat-like behavior by maintaining -/// a constant content offset from the bottom. Additionally, it is capable of handling autosizing cells and -/// supplementary views. -/// -/// ### Custom Properties: -/// `CollectionViewChatLayout.delegate` -/// -/// `CollectionViewChatLayout.settings` -/// -/// `CollectionViewChatLayout.keepContentOffsetAtBottomOnBatchUpdates` -/// -/// `CollectionViewChatLayout.processOnlyVisibleItemsOnAnimatedBatchUpdates` -/// -/// `CollectionViewChatLayout.visibleBounds` -/// -/// `CollectionViewChatLayout.layoutFrame` -/// -/// ### Custom Methods: -/// `CollectionViewChatLayout.getContentOffsetSnapshot(...)` -/// -/// `CollectionViewChatLayout.restoreContentOffset(...)` +// A collection view layout designed to display items in a grid similar to `UITableView`, while aligning them to the +// leading or trailing edge of the `UICollectionView`. This layout facilitates chat-like behavior by maintaining +// a constant content offset from the bottom. Additionally, it is capable of handling autosizing cells and +// supplementary views. +// +// ### Custom Properties: +// `CollectionViewChatLayout.delegate` +// +// `CollectionViewChatLayout.settings` +// +// `CollectionViewChatLayout.keepContentOffsetAtBottomOnBatchUpdates` +// +// `CollectionViewChatLayout.processOnlyVisibleItemsOnAnimatedBatchUpdates` +// +// `CollectionViewChatLayout.visibleBounds` +// +// `CollectionViewChatLayout.layoutFrame` +// +// ### Custom Methods: +// `CollectionViewChatLayout.getContentOffsetSnapshot(...)` +// +// `CollectionViewChatLayout.restoreContentOffset(...)` open class CollectionViewChatLayout: NSUICollectionViewLayout { // MARK: Custom Properties @@ -75,7 +75,7 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { public var keepContentAtBottomOfVisibleArea: Bool = false /// Sometimes `UIScrollView` can behave weirdly if there are too many corrections in it's `contentOffset` during the animation. Especially when content size of the `UIScrollView` - // is getting smaller first and then expands again as the newly appearing cells sizes are being calculated. That is why `CollectionViewChatLayout` + /// is getting smaller first and then expands again as the newly appearing cells sizes are being calculated. That is why `CollectionViewChatLayout` /// tries to process only the elements that are currently visible on the screen. But often it is not needed. This flag allows you to have fine control over this behaviour. /// It set to `true` by default to keep the compatibility with the older versions of the library. /// @@ -215,9 +215,9 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { private var cachedCollectionViewInset: NSUIEdgeInsets? - // These properties are used to keep the layout attributes copies used for insert/delete - // animations up-to-date as items are self-sized. If we don't keep these copies up-to-date, then - // animations will start from the estimated height. + /// These properties are used to keep the layout attributes copies used for insert/delete + /// animations up-to-date as items are self-sized. If we don't keep these copies up-to-date, then + /// animations will start from the estimated height. private var attributesForPendingAnimations = [ItemKind: [ItemPath: ChatLayoutAttributes]]() private var invalidatedAttributes = [ItemKind: Set]() @@ -253,7 +253,7 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { return isUserInitiatedScrolling && !controller.isAnimatedBoundsChange #endif } - + // private let logger = Logger(subsystem: "com.JH.CollectionViewChatLayout", category: "CollectionViewChatLayout") // MARK: Constructors @@ -371,7 +371,7 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { #if canImport(AppKit) && !targetEnvironment(macCatalyst) collectionView?.observeLiveScroll() #endif - + guard let collectionView, !prepareActions.isEmpty else { return @@ -528,8 +528,10 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { } /// Retrieves the layout attributes for the specified supplementary view. - open override func layoutAttributesForSupplementaryView(ofKind elementKind: String, - at indexPath: IndexPath) -> NSUICollectionViewLayoutAttributes? { + open override func layoutAttributesForSupplementaryView( + ofKind elementKind: String, + at indexPath: IndexPath + ) -> NSUICollectionViewLayoutAttributes? { guard !dontReturnAttributes else { return nil } @@ -577,8 +579,10 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { // MARK: Context Invalidation /// Asks the layout object if changes to a self-sizing cell require a layout update. - open override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: NSUICollectionViewLayoutAttributes, - withOriginalAttributes originalAttributes: NSUICollectionViewLayoutAttributes) -> Bool { + open override func shouldInvalidateLayout( + forPreferredLayoutAttributes preferredAttributes: NSUICollectionViewLayoutAttributes, + withOriginalAttributes originalAttributes: NSUICollectionViewLayoutAttributes + ) -> Bool { guard let preferredAttributesItemPath = preferredAttributes.platformIndexPath?.itemPath, let preferredMessageAttributes = preferredAttributes as? ChatLayoutAttributes, let item = controller.item(for: preferredAttributesItemPath, kind: preferredMessageAttributes.kind, at: state) else { @@ -594,8 +598,10 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { } /// Retrieves a context object that identifies the portions of the layout that should change in response to dynamic cell changes. - open override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: NSUICollectionViewLayoutAttributes, - withOriginalAttributes originalAttributes: NSUICollectionViewLayoutAttributes) -> NSUICollectionViewLayoutInvalidationContext { + open override func invalidationContext( + forPreferredLayoutAttributes preferredAttributes: NSUICollectionViewLayoutAttributes, + withOriginalAttributes originalAttributes: NSUICollectionViewLayoutAttributes + ) -> NSUICollectionViewLayoutInvalidationContext { guard let preferredMessageAttributes = preferredAttributes as? ChatLayoutAttributes, let preferredAttributesIndexPath = preferredMessageAttributes.platformIndexPath, controller.item(for: preferredAttributesIndexPath.itemPath, kind: .cell, at: state) != nil @@ -860,12 +866,23 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { let initialIndexPath = controller.itemPath(by: itemIdentifier, kind: .cell, at: .beforeUpdate) { attributes = controller.itemAttributes(for: initialIndexPath, kind: .cell, at: .beforeUpdate)?.typedCopy() ?? ChatLayoutAttributes(kind: .cell, indexPath: itemIndexPath) attributes?.indexPath = itemIndexPath + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + +// if controller.reloadedIndexes.contains(itemIndexPath) || controller.reconfiguredIndexes.contains(itemIndexPath) || controller.reloadedSectionsIndexes.contains(itemPath.section) { +// // It is needed to position the new cell in the middle of the old cell on ios 12 +// attributesForPendingAnimations[.cell]?[itemPath] = attributes +// } + + #endif + + #if canImport(UIKit) if #unavailable(iOS 13.0) { if controller.reloadedIndexes.contains(itemIndexPath) || controller.reconfiguredIndexes.contains(itemIndexPath) || controller.reloadedSectionsIndexes.contains(itemPath.section) { // It is needed to position the new cell in the middle of the old cell on ios 12 attributesForPendingAnimations[.cell]?[itemPath] = attributes } } + #endif } else { attributes = controller.itemAttributes(for: itemPath, kind: .cell, at: .beforeUpdate) } @@ -930,8 +947,10 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { // MARK: - Supplementary View Appearance Animation /// Retrieves the starting layout information for a supplementary view being inserted into the collection view. - open override func initialLayoutAttributesForAppearingSupplementaryElement(ofKind elementKind: String, - at elementIndexPath: IndexPath) -> NSUICollectionViewLayoutAttributes? { + open override func initialLayoutAttributesForAppearingSupplementaryElement( + ofKind elementKind: String, + at elementIndexPath: IndexPath + ) -> NSUICollectionViewLayoutAttributes? { var attributes: ChatLayoutAttributes? let kind = ItemKind(elementKind) @@ -953,12 +972,21 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { attributes = controller.itemAttributes(for: initialIndexPath, kind: kind, at: .beforeUpdate)?.typedCopy() ?? ChatLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: elementIndexPath) attributes?.indexPath = elementIndexPath + #if canImport(AppKit) && !targetEnvironment(macCatalyst) +// if controller.reloadedSectionsIndexes.contains(elementPath.section) { +// // It is needed to position the new cell in the middle of the old cell on ios 12 +// attributesForPendingAnimations[kind]?[elementPath] = attributes +// } + #endif + + #if canImport(UIKit) if #unavailable(iOS 13.0) { if controller.reloadedSectionsIndexes.contains(elementPath.section) { // It is needed to position the new cell in the middle of the old cell on ios 12 attributesForPendingAnimations[kind]?[elementPath] = attributes } } + #endif } else { attributes = controller.itemAttributes(for: elementPath, kind: kind, at: .beforeUpdate) } @@ -970,8 +998,10 @@ open class CollectionViewChatLayout: NSUICollectionViewLayout { } /// Retrieves the final layout information for a supplementary view that is about to be removed from the collection view. - open override func finalLayoutAttributesForDisappearingSupplementaryElement(ofKind elementKind: String, - at elementIndexPath: IndexPath) -> NSUICollectionViewLayoutAttributes? { + open override func finalLayoutAttributesForDisappearingSupplementaryElement( + ofKind elementKind: String, + at elementIndexPath: IndexPath + ) -> NSUICollectionViewLayoutAttributes? { var attributes: ChatLayoutAttributes? let kind = ItemKind(elementKind) diff --git a/ChatLayout/Classes/Core/Extensions/CollectionView+Extension.swift b/ChatLayout/Classes/Core/Extensions/CollectionView+Extension.swift index 4483c67..7d350fb 100644 --- a/ChatLayout/Classes/Core/Extensions/CollectionView+Extension.swift +++ b/ChatLayout/Classes/Core/Extensions/CollectionView+Extension.swift @@ -7,7 +7,7 @@ extension NSCollectionView { var contentOffset: CGPoint { set { enclosingScrollView?.contentView.scroll(to: newValue) -// animator().scroll(newValue) + enclosingScrollView.map { enclosingScrollView?.reflectScrolledClipView($0.contentView) } } get { enclosingScrollView?.contentView.bounds.origin ?? visibleRect.origin diff --git a/ChatLayout/Classes/Extras/CellLayoutContainerView.swift b/ChatLayout/Classes/Extras/CellLayoutContainerView.swift index 0605e2d..ae165ad 100644 --- a/ChatLayout/Classes/Extras/CellLayoutContainerView.swift +++ b/ChatLayout/Classes/Extras/CellLayoutContainerView.swift @@ -156,10 +156,10 @@ public final class CellLayoutContainerView: NSUIView { #if canImport(AppKit) && !targetEnvironment(macCatalyst) return ( - centerX: view.centerXAnchor.constraint(equalTo: customLayoutMarginsGuide.centerXAnchor, priority: preferredPriority), - centerY: view.centerYAnchor.constraint(equalTo: customLayoutMarginsGuide.centerYAnchor, priority: preferredPriority) + centerX: view.centerXAnchor.constraint(equalTo: centerXAnchor, priority: preferredPriority), + centerY: view.centerYAnchor.constraint(equalTo: centerYAnchor, priority: preferredPriority) ) #endif @@ -225,10 +225,10 @@ public final class EdgeAligningView: NSUIView { private func buildRigidConstraints(_ view: NSUIView) -> [Edge: NSLayoutConstraint] { #if canImport(AppKit) && !targetEnvironment(macCatalyst) - return [.top: view.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor, priority: preferredPriority), - .bottom: view.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor, priority: preferredPriority), - .leading: view.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor, priority: preferredPriority), - .trailing: view.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor, priority: preferredPriority)] + return [.top: view.topAnchor.constraint(equalTo: topAnchor, priority: preferredPriority), + .bottom: view.bottomAnchor.constraint(equalTo: bottomAnchor, priority: preferredPriority), + .leading: view.leadingAnchor.constraint(equalTo: leadingAnchor, priority: preferredPriority), + .trailing: view.trailingAnchor.constraint(equalTo: trailingAnchor, priority: preferredPriority)] #endif #if canImport(UIKit) @@ -241,10 +241,10 @@ public final class EdgeAligningView: NSUIView { private func buildFlexibleConstraints(_ view: NSUIView) -> [Edge: NSLayoutConstraint] { #if canImport(AppKit) && !targetEnvironment(macCatalyst) - return [.top: view.topAnchor.constraint(greaterThanOrEqualTo: customLayoutMarginsGuide.topAnchor, priority: preferredPriority), - .bottom: view.bottomAnchor.constraint(lessThanOrEqualTo: customLayoutMarginsGuide.bottomAnchor, priority: preferredPriority), - .leading: view.leadingAnchor.constraint(greaterThanOrEqualTo: customLayoutMarginsGuide.leadingAnchor, priority: preferredPriority), - .trailing: view.trailingAnchor.constraint(lessThanOrEqualTo: customLayoutMarginsGuide.trailingAnchor, priority: preferredPriority)] + return [.top: view.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, priority: preferredPriority), + .bottom: view.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, priority: preferredPriority), + .leading: view.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, priority: preferredPriority), + .trailing: view.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, priority: preferredPriority)] #endif #if canImport(UIKit) diff --git a/ChatLayout/Classes/Extras/Extensions/NSView+LayoutMargins.swift b/ChatLayout/Classes/Extras/Extensions/NSView+LayoutMargins.swift deleted file mode 100644 index 6e8d35f..0000000 --- a/ChatLayout/Classes/Extras/Extensions/NSView+LayoutMargins.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// BaseView.swift -// Pods -// -// Created by JH on 2024/10/11. -// - -import Foundation - -#if canImport(AppKit) && !targetEnvironment(macCatalyst) -import AppKit - -extension NSView { - private static var layoutMarginsKey = malloc(1)! - - public var layoutMargins: NSUIEdgeInsets { - set { - objc_setAssociatedObject(self, Self.layoutMarginsKey, NSValue(edgeInsets: newValue), .OBJC_ASSOCIATION_COPY_NONATOMIC) - setupLayoutGuideConstraints() - } - get { - if let layoutMargins = objc_getAssociatedObject(self, Self.layoutMarginsKey) as? NSValue { - return layoutMargins.edgeInsetsValue - } else { - return .zero - } - } - } - - private static var customLayoutMarginsGuideKey = malloc(1)! - - public var customLayoutMarginsGuide: NSLayoutGuide { - if let layoutGuide = objc_getAssociatedObject(self, Self.customLayoutMarginsGuideKey) as? NSLayoutGuide { - return layoutGuide - } else { - let layoutGuide = NSLayoutGuide() - objc_setAssociatedObject(self, Self.customLayoutMarginsGuideKey, layoutGuide, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - defer { - commonInit() - } - return layoutGuide - } - } - - private class LayoutConstraints: NSObject { - var topConstraint: NSLayoutConstraint? - - var bottomConstraint: NSLayoutConstraint? - - var leftConstraint: NSLayoutConstraint? - - var rightConstraint: NSLayoutConstraint? - } - - private static var layoutConstraintsKey = malloc(1)! - - private var layoutConstraints: LayoutConstraints { - if let layoutGuide = objc_getAssociatedObject(self, Self.layoutConstraintsKey) as? LayoutConstraints { - return layoutGuide - } else { - let layoutGuide = LayoutConstraints() - objc_setAssociatedObject(self, Self.layoutConstraintsKey, layoutGuide, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return layoutGuide - } - } - - private func setupLayoutGuideConstraints() { - layoutConstraints.topConstraint?.constant = layoutMargins.top - layoutConstraints.bottomConstraint?.constant = -layoutMargins.bottom - layoutConstraints.leftConstraint?.constant = layoutMargins.left - layoutConstraints.rightConstraint?.constant = -layoutMargins.right - } - - private func commonInit() { - addLayoutGuide(customLayoutMarginsGuide) - let topConstraint = customLayoutMarginsGuide.topAnchor.constraint(equalTo: topAnchor) - let bottomConstraint = customLayoutMarginsGuide.bottomAnchor.constraint(equalTo: bottomAnchor) - let leftConstraint = customLayoutMarginsGuide.leftAnchor.constraint(equalTo: leftAnchor) - let rightConstraint = customLayoutMarginsGuide.rightAnchor.constraint(equalTo: rightAnchor) - NSLayoutConstraint.activate([ - topConstraint, - bottomConstraint, - leftConstraint, - rightConstraint, - ]) - layoutConstraints.topConstraint = topConstraint - layoutConstraints.bottomConstraint = bottomConstraint - layoutConstraints.leftConstraint = leftConstraint - layoutConstraints.rightConstraint = rightConstraint - } -} - -#endif diff --git a/ChatLayout/Classes/Extras/ImageMaskedView.swift b/ChatLayout/Classes/Extras/ImageMaskedView.swift index 7d0ffab..62407e5 100644 --- a/ChatLayout/Classes/Extras/ImageMaskedView.swift +++ b/ChatLayout/Classes/Extras/ImageMaskedView.swift @@ -87,10 +87,10 @@ public final class ImageMaskedView: NSUIView { customView.translatesAutoresizingMaskIntoConstraints = false #if canImport(AppKit) && !targetEnvironment(macCatalyst) NSLayoutConstraint.activate([ - customView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - customView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), - customView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - customView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), + customView.topAnchor.constraint(equalTo: topAnchor), + customView.bottomAnchor.constraint(equalTo: bottomAnchor), + customView.leadingAnchor.constraint(equalTo: leadingAnchor), + customView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) #endif diff --git a/ChatLayout/Classes/Extras/MessageContainerView.swift b/ChatLayout/Classes/Extras/MessageContainerView.swift index 51b0808..563e653 100644 --- a/ChatLayout/Classes/Extras/MessageContainerView.swift +++ b/ChatLayout/Classes/Extras/MessageContainerView.swift @@ -92,10 +92,10 @@ public final class MessageContainerView: NSUIView { customView.translatesAutoresizingMaskIntoConstraints = false #if canImport(AppKit) && !targetEnvironment(macCatalyst) NSLayoutConstraint.activate([ - customView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - customView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), - customView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - customView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), + customView.topAnchor.constraint(equalTo: topAnchor), + customView.bottomAnchor.constraint(equalTo: bottomAnchor), + customView.leadingAnchor.constraint(equalTo: leadingAnchor), + customView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) #endif diff --git a/ChatLayout/Classes/Extras/SwappingContainerView.swift b/ChatLayout/Classes/Extras/SwappingContainerView.swift index acaf3fa..8dc1880 100644 --- a/ChatLayout/Classes/Extras/SwappingContainerView.swift +++ b/ChatLayout/Classes/Extras/SwappingContainerView.swift @@ -343,14 +343,14 @@ public final class SwappingContainerView NSEdgeInsets { -// layoutMargins + safeAreaInsets -// } } extension NSEdgeInsets { diff --git a/Example/ChatLayout/Chat/Model/DefaultRandomDataProvider.swift b/Example/ChatLayout/Chat/Model/DefaultRandomDataProvider.swift index fd7f69b..c154304 100644 --- a/Example/ChatLayout/Chat/Model/DefaultRandomDataProvider.swift +++ b/Example/ChatLayout/Chat/Model/DefaultRandomDataProvider.swift @@ -143,23 +143,23 @@ final class DefaultRandomDataProvider: RandomDataProvider { guard enableNewMessages else { return } -// let message = createRandomMessage() -// delegate?.received(messages: [message]) -// -// if message.userId != receiverId { -// if Int.random(in: 0...1) == 0 { -// lastReceivedUUID = message.id -// delegate?.lastReceivedIdChanged(to: message.id) -// } -// if Int.random(in: 0...3) == 0 { -// lastReadUUID = lastReceivedUUID -// lastReceivedUUID = message.id -// delegate?.lastReadIdChanged(to: message.id) -// } -// } -// -// restartMessageTimer() -// restartTypingTimer() + let message = createRandomMessage() + delegate?.received(messages: [message]) + + if message.userId != receiverId { + if Int.random(in: 0...1) == 0 { + lastReceivedUUID = message.id + delegate?.lastReceivedIdChanged(to: message.id) + } + if Int.random(in: 0...3) == 0 { + lastReadUUID = lastReceivedUUID + lastReceivedUUID = message.id + delegate?.lastReadIdChanged(to: message.id) + } + } + + restartMessageTimer() + restartTypingTimer() } @objc @@ -167,8 +167,8 @@ final class DefaultRandomDataProvider: RandomDataProvider { guard enableTyping else { return } -// typingState = typingState == .idle ? TypingState.typing : .idle -// delegate?.typingStateChanged(to: typingState) + typingState = typingState == .idle ? TypingState.typing : .idle + delegate?.typingStateChanged(to: typingState) } private func restartMessageTimer() { @@ -184,22 +184,22 @@ final class DefaultRandomDataProvider: RandomDataProvider { } private func createRandomMessage(date: Date = Date()) -> RawMessage { - let sender = allUsersIds[0] // allUsersIds.first!// + let sender = allUsersIds[Int.random(in: 0.. Bool { -// true - false + true +// false } public func shouldPresentFooter(_ chatLayout: CollectionViewChatLayout, at sectionIndex: Int) -> Bool { diff --git a/Example/ChatLayout/Chat/View/Date Accessory View/DateAccessoryView.swift b/Example/ChatLayout/Chat/View/Date Accessory View/DateAccessoryView.swift index 82a5c94..b87094f 100644 --- a/Example/ChatLayout/Chat/View/Date Accessory View/DateAccessoryView.swift +++ b/Example/ChatLayout/Chat/View/Date Accessory View/DateAccessoryView.swift @@ -51,13 +51,24 @@ final class DateAccessoryView: NSUIView { translatesAutoresizingMaskIntoConstraints = false addSubview(accessoryView) - + + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + NSLayoutConstraint.activate([ + accessoryView.leadingAnchor.constraint(equalTo: leadingAnchor), + accessoryView.trailingAnchor.constraint(equalTo: trailingAnchor), + accessoryView.topAnchor.constraint(equalTo: topAnchor), + accessoryView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + #endif + + #if canImport(UIKit) NSLayoutConstraint.activate([ accessoryView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), accessoryView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), accessoryView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), accessoryView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) + #endif accessoryView.translatesAutoresizingMaskIntoConstraints = false diff --git a/Example/ChatLayout/Chat/View/Editing Accessory View/EditingAccessoryView.swift b/Example/ChatLayout/Chat/View/Editing Accessory View/EditingAccessoryView.swift index 586eafc..28c0783 100644 --- a/Example/ChatLayout/Chat/View/Editing Accessory View/EditingAccessoryView.swift +++ b/Example/ChatLayout/Chat/View/Editing Accessory View/EditingAccessoryView.swift @@ -51,12 +51,23 @@ final class EditingAccessoryView: NSUIView, StaticViewFactory { addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + NSLayoutConstraint.activate([ + button.leadingAnchor.constraint(equalTo: leadingAnchor), + button.trailingAnchor.constraint(equalTo: trailingAnchor), + button.topAnchor.constraint(equalTo: topAnchor), + button.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) +#endif + +#if canImport(UIKit) NSLayoutConstraint.activate([ button.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), button.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), button.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), button.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) +#endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) button.title = "Delete" diff --git a/Example/ChatLayout/Chat/View/Image View/ImageView.swift b/Example/ChatLayout/Chat/View/Image View/ImageView.swift index 3d18384..468219f 100644 --- a/Example/ChatLayout/Chat/View/Image View/ImageView.swift +++ b/Example/ChatLayout/Chat/View/Image View/ImageView.swift @@ -159,10 +159,10 @@ final class ImageView: NSUIView, ContainerCollectionViewCellDelegate { #if canImport(AppKit) && !targetEnvironment(macCatalyst) NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - stackView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), - stackView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) #endif diff --git a/Example/ChatLayout/Chat/View/Other/BezierMaskedView.swift b/Example/ChatLayout/Chat/View/Other/BezierMaskedView.swift index 891ef01..6f7b05f 100644 --- a/Example/ChatLayout/Chat/View/Other/BezierMaskedView.swift +++ b/Example/ChatLayout/Chat/View/Other/BezierMaskedView.swift @@ -117,10 +117,10 @@ final class BezierMaskedView: NSUIView { customView.translatesAutoresizingMaskIntoConstraints = false #if canImport(AppKit) && !targetEnvironment(macCatalyst) NSLayoutConstraint.activate([ - customView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - customView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), - customView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - customView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), + customView.topAnchor.constraint(equalTo: topAnchor), + customView.bottomAnchor.constraint(equalTo: bottomAnchor), + customView.leadingAnchor.constraint(equalTo: leadingAnchor), + customView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) #endif diff --git a/Example/ChatLayout/Chat/View/Other/FullCellContentBubbleController.swift b/Example/ChatLayout/Chat/View/Other/FullCellContentBubbleController.swift index ea01aeb..5653d76 100644 --- a/Example/ChatLayout/Chat/View/Other/FullCellContentBubbleController.swift +++ b/Example/ChatLayout/Chat/View/Other/FullCellContentBubbleController.swift @@ -39,7 +39,11 @@ final class FullCellContentBubbleController: BubbleControl NSUIView.performWithoutAnimation { bubbleView.backgroundColor = .clear +#if canImport(UIKit) + bubbleView.customView.layoutMargins = .zero + +#endif } } } diff --git a/Example/ChatLayout/Chat/View/Other/MainContainerView.swift b/Example/ChatLayout/Chat/View/Other/MainContainerView.swift index 46a559f..930bfbb 100644 --- a/Example/ChatLayout/Chat/View/Other/MainContainerView.swift +++ b/Example/ChatLayout/Chat/View/Other/MainContainerView.swift @@ -92,10 +92,10 @@ final class MainContainerView: BubbleController { } NSUIView.performWithoutAnimation { let marginOffset: CGFloat = type.isIncoming ? -Constants.tailSize : Constants.tailSize + #if canImport(UIKit) let edgeInsets = NSUIEdgeInsets(top: 8, left: 16 - marginOffset, bottom: 8, right: 16 + marginOffset) bubbleView.layoutMargins = edgeInsets + #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) bubbleView.backgroundColor = type.isIncoming ? .windowBackgroundColor : .systemBlue diff --git a/Example/ChatLayout/Chat/View/Status View/StatusView.swift b/Example/ChatLayout/Chat/View/Status View/StatusView.swift index 577ce55..6740ba6 100644 --- a/Example/ChatLayout/Chat/View/Status View/StatusView.swift +++ b/Example/ChatLayout/Chat/View/Status View/StatusView.swift @@ -33,10 +33,10 @@ final class StatusView: NSUIView, StaticViewFactory { setupSubviews() } -#if canImport(AppKit) && !targetEnvironment(macCatalyst) + #if canImport(AppKit) && !targetEnvironment(macCatalyst) override var isFlipped: Bool { true } -#endif - + #endif + private func setupSubviews() { translatesAutoresizingMaskIntoConstraints = false addSubview(imageView) @@ -48,10 +48,10 @@ final class StatusView: NSUIView, StaticViewFactory { imageView.translatesAutoresizingMaskIntoConstraints = false #if canImport(AppKit) && !targetEnvironment(macCatalyst) NSLayoutConstraint.activate([ - imageView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), - imageView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - imageView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), + imageView.leadingAnchor.constraint(equalTo: leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) #endif diff --git a/Example/ChatLayout/Chat/View/Text Message View/TextMessageView.swift b/Example/ChatLayout/Chat/View/Text Message View/TextMessageView.swift index 995ac5b..37e055d 100644 --- a/Example/ChatLayout/Chat/View/Text Message View/TextMessageView.swift +++ b/Example/ChatLayout/Chat/View/Text Message View/TextMessageView.swift @@ -38,24 +38,6 @@ final class TextMessageView: NSView, ContainerCollectionViewCellDelegate { override var isFlipped: Bool { true } - // Uncomment this method to test the performance without calculating text cell size using autolayout - // For the better illustration set DefaultRandomDataProvider.enableRichContent/enableNewMessages - // to false -// func preferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes) -> ChatLayoutAttributes? { -// viewPortWidth = layoutAttributes.layoutFrame.width -// guard let text = controller?.text as NSString? else { -// return layoutAttributes -// } -// let maxWidth = viewPortWidth * Constants.maxWidth -// var rect = text.boundingRect(with: CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude), -// options: [.usesLineFragmentOrigin, .usesFontLeading], -// attributes: [NSAttributedString.Key.font: textView.font as Any], context: nil) -// rect = rect.insetBy(dx: 0, dy: -8) -// layoutAttributes.size = CGSize(width: layoutAttributes.layoutFrame.width, height: rect.height) -// setupSize() -// return layoutAttributes -// } - func apply(_ layoutAttributes: ChatLayoutAttributes) { viewPortWidth = layoutAttributes.layoutFrame.width setupSize() @@ -89,10 +71,10 @@ final class TextMessageView: NSView, ContainerCollectionViewCellDelegate { textView.font = .preferredFont(forTextStyle: .body) addSubview(textView) NSLayoutConstraint.activate([ - textView.topAnchor.constraint(equalTo: customLayoutMarginsGuide.topAnchor), - textView.bottomAnchor.constraint(equalTo: customLayoutMarginsGuide.bottomAnchor), - textView.leadingAnchor.constraint(equalTo: customLayoutMarginsGuide.leadingAnchor), - textView.trailingAnchor.constraint(equalTo: customLayoutMarginsGuide.trailingAnchor), + textView.topAnchor.constraint(equalTo: topAnchor), + textView.bottomAnchor.constraint(equalTo: bottomAnchor), + textView.leadingAnchor.constraint(equalTo: leadingAnchor), + textView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) textViewWidthConstraint = textView.widthAnchor.constraint(lessThanOrEqualToConstant: viewPortWidth) textViewWidthConstraint?.isActive = true