Skip to content

Commit 5d900fd

Browse files
authored
Merge pull request #244 from surfstudio/feature/SPT-1478/add-interaction-to-message-view
[SPT-1478] Интерактивность MessageView
2 parents 9c1e698 + 21ab601 commit 5d900fd

File tree

4 files changed

+153
-19
lines changed

4 files changed

+153
-19
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// DataDetectionStyle.swift
3+
// ReactiveDataDisplayManager
4+
//
5+
// Created by Антон Голубейков on 13.07.2023.
6+
//
7+
#if os(iOS)
8+
import Foundation
9+
import UIKit
10+
11+
public struct DataDetectionStyle: Equatable {
12+
13+
// MARK: - Nested types
14+
15+
public typealias Handler = (URL) -> Void
16+
17+
// MARK: - Properties
18+
19+
public let id: AnyHashable
20+
public let linkTextAttributes: [NSAttributedString.Key: Any]
21+
public var handler: Handler?
22+
public var dataDetectorTypes: UIDataDetectorTypes = []
23+
24+
public init(id: AnyHashable, linkTextAttributes: [NSAttributedString.Key: Any], handler: @escaping Handler, dataDetectorTypes: UIDataDetectorTypes) {
25+
self.id = id
26+
self.linkTextAttributes = linkTextAttributes
27+
self.handler = handler
28+
self.dataDetectorTypes = dataDetectorTypes
29+
}
30+
31+
// MARK: - Equatable
32+
33+
public static func == (lhs: DataDetectionStyle, rhs: DataDetectionStyle) -> Bool {
34+
lhs.id == rhs.id &&
35+
areDictionariesEqual(lhs.linkTextAttributes, rhs.linkTextAttributes) &&
36+
lhs.dataDetectorTypes == rhs.dataDetectorTypes
37+
}
38+
39+
}
40+
41+
// MARK: - Private extension
42+
43+
private extension DataDetectionStyle {
44+
45+
private static func areDictionariesEqual(_ lhs: [NSAttributedString.Key: Any]?, _ rhs: [NSAttributedString.Key: Any]?) -> Bool {
46+
guard let lhs = lhs, let rhs = rhs else {
47+
return lhs == nil && rhs == nil
48+
}
49+
50+
return NSDictionary(dictionary: lhs).isEqual(to: rhs)
51+
}
52+
53+
}
54+
#endif

Components/Sources/Common/Utils/ViewWrapper.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public protocol ViewWrapper: ConfigurableItem {
1515
/// Inner configurable view with content
1616
var nestedView: NestedView { get }
1717

18+
/// View that will be used as container for `nestedView`
19+
var contentView: UIView { get }
20+
1821
/// Previous value of `Alignment` applyed to `nestedView`
1922
var cachedAlignment: Alignment? { get set }
2023

@@ -46,11 +49,11 @@ private extension ViewWrapper where Self: UIView {
4649
nestedView.removeFromSuperview()
4750
switch alignment {
4851
case .trailing(let insets):
49-
wrapWithLeadingGreaterThenOrEqualRule(subview: nestedView, with: insets)
52+
contentView.wrapWithLeadingGreaterThenOrEqualRule(subview: nestedView, with: insets)
5053
case .leading(let insets):
51-
wrapWithTrailingLessThenOrEqualRule(subview: nestedView, with: insets)
54+
contentView.wrapWithTrailingLessThenOrEqualRule(subview: nestedView, with: insets)
5255
case .all(let insets):
53-
wrap(subview: nestedView, with: insets)
56+
contentView.wrap(subview: nestedView, with: insets)
5457
case .none:
5558
break
5659
}

Components/Sources/Common/Views/MessageView.swift

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class MessageView: UIView {
1414
// MARK: - Private properties
1515

1616
private var textView = UITextView(frame: .zero)
17+
private var dataDetectionHandler: DataDetectionStyle.Handler?
1718

1819
}
1920

@@ -103,6 +104,23 @@ extension MessageView: ConfigurableItem {
103104
return model
104105
})
105106
}
107+
108+
public static func dataDetection(_ value: DataDetectionStyle) -> Property {
109+
.init(closure: { model in
110+
var model = model
111+
model.set(dataDetection: value)
112+
return model
113+
})
114+
}
115+
116+
/// To set it to **false**, dataDetection and tapHandler must be nil
117+
public static func selectable(_ selectable: Bool) -> Property {
118+
.init(closure: { model in
119+
var model = model
120+
model.set(selectable: selectable)
121+
return model
122+
})
123+
}
106124
}
107125

108126
// MARK: - Public properties
@@ -115,6 +133,8 @@ extension MessageView: ConfigurableItem {
115133
private(set) public var alignment: Alignment = .all(.zero)
116134
private(set) public var internalEdgeInsets: UIEdgeInsets = .zero
117135
private(set) public var borderStyle: BorderStyle?
136+
private(set) public var dataDetection: DataDetectionStyle?
137+
private(set) public var selectable: Bool = false
118138

119139
// MARK: - Mutation
120140

@@ -150,6 +170,14 @@ extension MessageView: ConfigurableItem {
150170
self.borderStyle = border
151171
}
152172

173+
mutating func set(dataDetection: DataDetectionStyle) {
174+
self.dataDetection = dataDetection
175+
}
176+
177+
mutating func set(selectable: Bool) {
178+
self.selectable = selectable
179+
}
180+
153181
// MARK: - Builder
154182

155183
public static func build(@EditorBuilder<Property> content: (Property.Type) -> [Property]) -> Self {
@@ -163,9 +191,10 @@ extension MessageView: ConfigurableItem {
163191
// MARK: - Methods
164192

165193
public func configure(with model: Model) {
166-
167194
textView.backgroundColor = .clear
168195
textView.isEditable = false
196+
setIsSelectablePropertyIfNeeded(for: model)
197+
textView.isUserInteractionEnabled = true
169198
textView.isScrollEnabled = false
170199
configureTextView(textView, with: model)
171200
textView.textColor = model.textStyle.color
@@ -193,6 +222,11 @@ private extension MessageView {
193222
case .attributedString(let attributedString):
194223
textView.attributedText = attributedString
195224
}
225+
226+
textView.dataDetectorTypes = model.dataDetection?.dataDetectorTypes ?? []
227+
textView.linkTextAttributes = model.dataDetection?.linkTextAttributes
228+
textView.delegate = self
229+
dataDetectionHandler = model.dataDetection?.handler
196230
}
197231

198232
func applyBackground(style: BackgroundStyle) {
@@ -212,5 +246,28 @@ private extension MessageView {
212246
layer.maskedCorners = borderStyle.maskedCorners
213247
}
214248

249+
func handleDataDetection(_ data: URL) {
250+
dataDetectionHandler?(data)
251+
}
252+
253+
func setIsSelectablePropertyIfNeeded(for model: Model) {
254+
if model.dataDetection != nil {
255+
textView.isSelectable = true
256+
} else {
257+
textView.isSelectable = model.selectable
258+
}
259+
}
260+
261+
}
262+
263+
// MARK: - UITextViewDelegate
264+
265+
extension MessageView: UITextViewDelegate {
266+
267+
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
268+
handleDataDetection(URL)
269+
return false
270+
}
271+
215272
}
216273
#endif

Example/ReactiveDataDisplayManager/Table/ComponentsOverviewTableViewController/ComponentsOverviewTableViewController.swift

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,10 @@ final class ComponentsOverviewTableViewController: UIViewController {
8282
private let sentMessageStyle = TextStyle(color: .white,
8383
font: .systemFont(ofSize: 16, weight: .regular))
8484
private let sentMessageBorderStyle = BorderStyle(cornerRadius: 9,
85-
maskedCorners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner])
85+
maskedCorners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner])
8686
private lazy var sentMessageModel: MessageView.Model = .build { property in
87-
if Bool.random() {
88-
property.background(.solid(.systemBlue))
89-
} else {
90-
property.background(.solid(.rddm))
91-
}
87+
let backgorundColor: UIColor? = Bool.random() ? .systemBlue : .rddm
88+
property.background(.solid(backgorundColor))
9289
property.border(sentMessageBorderStyle)
9390
property.style(sentMessageStyle)
9491
property.textAlignment(.right)
@@ -101,21 +98,41 @@ final class ComponentsOverviewTableViewController: UIViewController {
10198
left: 5,
10299
bottom: 3,
103100
right: 5))
104-
property.text(.string("Lorem"))
101+
property.text(.string("Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet"))
102+
103+
let tapAction: () -> Void = {
104+
if var topController = UIApplication.shared.keyWindow?.rootViewController {
105+
while let presentedViewController = topController.presentedViewController {
106+
topController = presentedViewController
107+
}
108+
109+
let alertController = UIAlertController(title: "MessageView Tapped", message: "The MessageView was tapped!", preferredStyle: .alert)
110+
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
111+
topController.present(alertController, animated: true, completion: nil)
112+
}
113+
}
114+
105115
}
106116

107117
private lazy var sentMessageGenerator = MessageView.rddm.tableGenerator(with: sentMessageModel, and: .class)
108118

109119
// Recieved message
110120
private let recievedMessageStyle = TextStyle(color: .black, font: .systemFont(ofSize: 16, weight: .regular))
111121
private let recievedMessageBorderStyle = BorderStyle(cornerRadius: 9,
112-
maskedCorners: [
113-
.layerMinXMinYCorner,
114-
.layerMaxXMaxYCorner,
115-
.layerMaxXMinYCorner
116-
],
117-
borderWidth: 1,
118-
borderColor: UIColor.black.cgColor)
122+
maskedCorners: [
123+
.layerMinXMinYCorner,
124+
.layerMaxXMaxYCorner,
125+
.layerMaxXMinYCorner
126+
],
127+
borderWidth: 1,
128+
borderColor: UIColor.black.cgColor)
129+
let dataDetectionHandler: DataDetectionStyle.Handler = { url in
130+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
131+
}
132+
private lazy var dataDetection = DataDetectionStyle(id: "Basic handling of links using UIApplication",
133+
linkTextAttributes: [.foregroundColor: UIColor.blue],
134+
handler: dataDetectionHandler,
135+
dataDetectorTypes: [.link])
119136
private lazy var recievedMessageModel: MessageView.Model = .build { property in
120137
property.border(recievedMessageBorderStyle)
121138
property.style(recievedMessageStyle)
@@ -127,7 +144,9 @@ final class ComponentsOverviewTableViewController: UIViewController {
127144
left: 5,
128145
bottom: 3,
129146
right: 5))
130-
property.text(.string("Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet"))
147+
property.text(.string("Check out link: https://stackoverflow.com/"))
148+
property.dataDetection(dataDetection)
149+
property.selectable(true)
131150
}
132151

133152
private lazy var recievedMessageGenerator = MessageView.rddm.tableGenerator(with: recievedMessageModel, and: .class)
@@ -143,6 +162,7 @@ final class ComponentsOverviewTableViewController: UIViewController {
143162
super.viewDidLoad()
144163
tableView.accessibilityIdentifier = "ComponentsOverviewTableViewController"
145164
tableView.separatorStyle = .none
165+
tableView.allowsSelection = false
146166
fillAdapter()
147167
}
148168

0 commit comments

Comments
 (0)