// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit import MatrixSDK /// Text attachment for pills display. @available (iOS 15.0, *) @objcMembers class PillTextAttachment: NSTextAttachment { // MARK: - Properties /// Return `PillTextAttachmentData` contained in the text attachment. var data: PillTextAttachmentData? { get { guard let contents = contents else { return nil } return try? Self.serializationService.deserialize(contents) } set { guard let newValue = newValue else { contents = nil return } contents = try? Self.serializationService.serialize(newValue) updateBounds() } } private static let serializationService: SerializationServiceType = SerializationService() // MARK: - Init override init(data contentData: Data?, ofType uti: String?) { super.init(data: contentData, ofType: uti) updateBounds() } convenience init?(attachmentData: PillTextAttachmentData) { guard let encodedData = try? Self.serializationService.serialize(attachmentData) else { return nil } self.init(data: encodedData, ofType: PillsFormatter.pillUTType) } /// Create a Mention Pill text attachment for given room member. /// /// - Parameters: /// - roomMember: the room member /// - isHighlighted: whether this pill should be highlighted /// - font: the text font convenience init?(withRoomMember roomMember: MXRoomMember, isHighlighted: Bool, font: UIFont) { let data = PillTextAttachmentData(pillType: .user(userId: roomMember.userId), items: [ .avatar(url: roomMember.avatarUrl, string: roomMember.displayname, matrixId: roomMember.userId), .text(roomMember.displayname) ], isHighlighted: isHighlighted, alpha: 1.0, font: font) guard let encodedData = try? Self.serializationService.serialize(data) else { return nil } self.init(data: encodedData, ofType: PillsFormatter.pillUTType) } required init?(coder: NSCoder) { super.init(coder: coder) updateBounds() } /// Computes size required to display a pill for given display text. /// /// - Parameters: /// - font: the text font /// - Returns: required size for pill func size(forFont font: UIFont) -> CGSize { guard let data else { MXLog.debug("[PillTextAttachment]: data are missing") return .zero } let sizes = PillAttachmentViewProvider.pillAttachmentViewSizes var width: CGFloat = 0 var textContent = "" for item in data.items { switch item { case .text(let text): textContent += text case .avatar, .asset, .spaceAvatar: width += sizes.avatarSideLength } } // add texts if !textContent.isEmpty { let label = UILabel(frame: .zero) label.font = font label.text = textContent width += label.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: sizes.pillBackgroundHeight)).width } // add spacing width += CGFloat(max(0, data.items.count - 1)) * sizes.itemSpacing // add margins switch data.items.first { case .asset, .avatar: width += sizes.avatarLeading + sizes.horizontalMargin default: width += 2 * sizes.horizontalMargin } return CGSize(width: width, height: sizes.pillHeight) } } // MARK: - Private @available (iOS 15.0, *) private extension PillTextAttachment { func updateBounds() { guard let data = data else { return } let pillSize = size(forFont: data.font) // Offset to align pill centerY with text centerY. let offset = data.font.descender + (data.font.lineHeight - pillSize.height) / 2.0 self.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: pillSize) } }