diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/Contents.json new file mode 100644 index 0000000..722103e --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_chat_response@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_chat_response@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@2x.png new file mode 100644 index 0000000..b20b5c1 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@3x.png new file mode 100644 index 0000000..8c090da Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_response.imageset/role_chat_response@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/Contents.json new file mode 100644 index 0000000..a34a740 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_chat_send@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_chat_send@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@2x.png new file mode 100644 index 0000000..2dc8770 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@3x.png new file mode 100644 index 0000000..ccd0037 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_bg_send.imageset/role_chat_send@3x.png differ diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/View/StreamChatBubbleCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/View/StreamChatBubbleCell.swift index 2c73cd0..26b8be2 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/View/StreamChatBubbleCell.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/View/StreamChatBubbleCell.swift @@ -7,6 +7,37 @@ final class StreamChatBubbleCell: UITableViewCell { private let bubbleView = UIView() private let messageLabel = UILabel() var onLongPress: (() -> Void)? + + lazy var sendBgImgView: UIImageView = { + guard let originalImage = UIImage(named: "role_chat_bg_send") else { + return UIImageView() + } + // 发送消息:尖尖在右上角,focus 点应该在右上角附近 + // 假设图片尺寸,focus 点设置为右上角区域(距离右边和顶部一定距离) + let imageSize = originalImage.size + let focusX = imageSize.width - 20.0 // 距离右边 20pt + let focusY = 20.0 // 距离顶部 20pt + let stretchedImage = originalImage.makeStretchable(from: originalImage, focus: CGPoint(x: focusX, y: focusY)) + let imgV = UIImageView(image: stretchedImage) + imgV.contentMode = .scaleToFill + return imgV + }() + + lazy var respBgImgView: UIImageView = { + guard let originalImage = UIImage(named: "role_chat_bg_response") else { + return UIImageView() + } + // 接收消息:尖尖在左上角,focus 点应该在左上角附近 + // focus 点设置为左上角区域(距离左边和顶部一定距离) + let imageSize = originalImage.size + let focusX = 20.0 // 距离左边 20pt + let focusY = 20.0 // 距离顶部 20pt + let stretchedImage = originalImage.makeStretchable(from: originalImage, focus: CGPoint(x: focusX, y: focusY)) + let imgV = UIImageView(image: stretchedImage) + imgV.contentMode = .scaleToFill + return imgV + }() + private lazy var longPressGesture: UILongPressGestureRecognizer = { let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:))) gesture.minimumPressDuration = 0.4 @@ -34,8 +65,8 @@ final class StreamChatBubbleCell: UITableViewCell { private func setupViews() { contentView.addSubview(bubbleView) - bubbleView.layer.cornerRadius = 18 - bubbleView.layer.masksToBounds = true + bubbleView.addSubview(sendBgImgView) + bubbleView.addSubview(respBgImgView) bubbleView.setContentHuggingPriority(.defaultLow, for: .horizontal) bubbleView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) contentView.addGestureRecognizer(longPressGesture) @@ -52,20 +83,30 @@ final class StreamChatBubbleCell: UITableViewCell { func configure(message: StreamChatMessageModel) { messageLabel.text = message.text + respBgImgView.isHidden = message.isSelf + sendBgImgView.isHidden = !message.isSelf if message.isSelf { - bubbleView.backgroundColor = UIColor.c.cpn + // 发送消息:深色背景,尖尖在右上角 messageLabel.textColor = .white + bubbleView.backgroundColor = .clear + bubbleView.snp.remakeConstraints { make in make.top.equalToSuperview().offset(6) make.bottom.equalToSuperview().offset(-6) make.width.lessThanOrEqualTo(UIScreen.width * 2.0 / 3.0) - make.width.greaterThanOrEqualTo(60) + make.width.greaterThanOrEqualTo(40) make.trailing.equalToSuperview().offset(-16) } + + sendBgImgView.snp.remakeConstraints { make in + make.edges.equalToSuperview() + } } else { - bubbleView.backgroundColor = UIColor.white.withAlphaComponent(0.2) - messageLabel.textColor = .white + // 接收消息:浅色背景,尖尖在左上角 + messageLabel.textColor = UIColor(hex: "#333333") + bubbleView.backgroundColor = .clear + bubbleView.snp.remakeConstraints { make in make.top.equalToSuperview().offset(6) make.bottom.equalToSuperview().offset(-6) @@ -73,6 +114,10 @@ final class StreamChatBubbleCell: UITableViewCell { make.width.greaterThanOrEqualTo(60) make.leading.equalToSuperview().offset(16) } + + respBgImgView.snp.remakeConstraints { make in + make.edges.equalToSuperview() + } } messageLabel.snp.remakeConstraints { make in diff --git a/Visual_Novel_iOS/Src/Utils/Extensions/UIImageExt.swift b/Visual_Novel_iOS/Src/Utils/Extensions/UIImageExt.swift index bb0357a..1315083 100755 --- a/Visual_Novel_iOS/Src/Utils/Extensions/UIImageExt.swift +++ b/Visual_Novel_iOS/Src/Utils/Extensions/UIImageExt.swift @@ -460,6 +460,33 @@ public extension UIImage { func pngBase64String() -> String? { return pngData()?.base64EncodedString() } + + /// 根据“焦点”生成四周可拉伸、四角不变的 UIImage + /// 返回的图 *本身* 就是 resizable,尺寸由外部 Auto Layout 决定 + func makeStretchable(from image: UIImage, + focus: CGPoint) -> UIImage { + + let scale = image.scale + let w = image.size.width * scale + let h = image.size.height * scale + let fx = focus.x * scale + let fy = focus.y * scale + + // 保护四角:以焦点为中心对称留 cap + let capLeft = min(fx, w - fx) * 0.5 + let capRight = w - capLeft + let capTop = min(fy, h - fy) * 0.5 + let capBottom = h - capTop + + let insets = UIEdgeInsets(top: capTop, + left: capLeft, + bottom: capBottom, + right: capRight) + + // 只生成可拉伸图,不立即渲染固定尺寸 + return image.resizableImage(withCapInsets: insets, + resizingMode: .stretch) + } /// Base 64 encoded JPEG data of the image. ///