Skip to content

Commit fbe949a

Browse files
Merge pull request #91 from contentful/layout-fixes
Layout fixes
2 parents 8843e2e + 8e71229 commit fbe949a

File tree

6 files changed

+60
-27
lines changed

6 files changed

+60
-27
lines changed

ContentfulRichTextRenderer.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Pod::Spec.new do |spec|
44
spec.name = "ContentfulRichTextRenderer"
5-
spec.version = "0.4.5"
5+
spec.version = "0.4.6"
66
spec.summary = "Swift library for rendering Contentful RichTextDocument."
77
spec.homepage = "https://github.com/contentful-labs/rich-text-renderer.swift"
88
spec.social_media_url = 'https://twitter.com/contentful'
@@ -15,7 +15,7 @@ Pod::Spec.new do |spec|
1515
spec.requires_arc = true
1616

1717
spec.swift_version = "5.2"
18-
spec.ios.deployment_target = "12.4"
18+
spec.ios.deployment_target = "13.0"
1919

2020
spec.source_files = "Sources/RichTextRenderer/**/*.swift"
2121

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import PackageDescription
44
let package = Package(
55
name: "ContentfulRichTextRenderer",
66
platforms: [
7-
.iOS(.v10)
7+
.iOS(.v13)
88
],
99
products: [
1010
.library(

RichTextRenderer.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@
819819
DYLIB_INSTALL_NAME_BASE = "@rpath";
820820
INFOPLIST_FILE = Sources/RichTextRenderer/Info.plist;
821821
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
822-
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
822+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
823823
LD_RUNPATH_SEARCH_PATHS = (
824824
"$(inherited)",
825825
"@executable_path/Frameworks",
@@ -848,7 +848,7 @@
848848
DYLIB_INSTALL_NAME_BASE = "@rpath";
849849
INFOPLIST_FILE = Sources/RichTextRenderer/Info.plist;
850850
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
851-
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
851+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
852852
LD_RUNPATH_SEARCH_PATHS = (
853853
"$(inherited)",
854854
"@executable_path/Frameworks",

Sources/RichTextRenderer/NodeRenderers/Table/SimpleImplementation/SimpleTableViewCell.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class SimpleTableViewCell: UIView, ResourceLinkBlockViewRepresentable {
5757
richTextViewController.view.trailingAnchor.constraint(equalTo: trailingAnchor)
5858
])
5959

60+
if isHeader {
61+
richTextViewController.view.overrideUserInterfaceStyle = .light
62+
}
63+
6064
richTextViewController.richTextDocument = RichTextDocument(content: nodes)
6165
richTextViewController.updateTextViewBackground(color: .clear)
6266
}

Sources/RichTextRenderer/Renderer/Default/DefaultStyleProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public final class DefaultStyleProvider: StyleProviding {
7575

7676
public init(
7777
baseFont: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize),
78-
baseColor: UIColor = UIColor.black, // Base color initialization
78+
baseColor: UIColor = UIColor.rtrLabel, // Base color initialization - adapts to dark mode
7979
monospacedFont: UIFont? = UIFont(name: "Menlo-Regular", size: UIFont.systemFontSize),
8080
hyperlinkColor: UIColor? = UIColor.systemBlue
8181
) {

Sources/RichTextRenderer/ViewController/RichTextViewController.swift

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ open class RichTextViewController: UIViewController, NSLayoutManagerDelegate, UI
172172
self.textStorage.beginEditing()
173173
self.textStorage.setAttributedString(output)
174174
self.textStorage.endEditing()
175+
176+
layoutElementsOnTextView(containerSize: textView.bounds.size)
175177

176178
self.calculateAndSetPreferredContentSize()
177179
self.applyTextViewStyles()
@@ -182,8 +184,35 @@ open class RichTextViewController: UIViewController, NSLayoutManagerDelegate, UI
182184
}
183185

184186
private func calculateAndSetPreferredContentSize() {
185-
let newContentSize = textView.sizeThatFits(textView.bounds.size)
186-
guard newContentSize != preferredContentSize else {
187+
// Base size from text content
188+
var newContentSize = textView.sizeThatFits(textView.bounds.size)
189+
190+
// If there are attachment views (like embedded resources) that sit below the text
191+
// they should be accounted for in preferredContentSize. Compute the bottom-most
192+
// point of all attachment subviews we've added and expand the height if needed.
193+
if !attachmentViews.isEmpty {
194+
// Convert attachment subviews' frames to textView coordinate space and find max bottom
195+
let maxAttachmentBottom = attachmentViews.values
196+
.compactMap { $0.superview == textView ? $0.frame.maxY : nil }
197+
.max() ?? 0
198+
199+
// textView content is inset by textView.textContainerInset; preferredContentSize
200+
// should be at least the bottom of the attachments plus bottom inset.
201+
let contentInsetBottom = textView.textContainerInset.bottom
202+
let attachmentsRequiredHeight = maxAttachmentBottom + contentInsetBottom
203+
204+
if attachmentsRequiredHeight > newContentSize.height {
205+
newContentSize.height = attachmentsRequiredHeight
206+
}
207+
}
208+
209+
// Avoid triggering tiny layout changes which could lead to layout loops. Only update
210+
// preferredContentSize if the change is meaningful (epsilon threshold).
211+
let epsilon: CGFloat = 0.5
212+
let heightDelta = abs(preferredContentSize.height - newContentSize.height)
213+
let widthDelta = abs(preferredContentSize.width - newContentSize.width)
214+
215+
guard heightDelta > epsilon || widthDelta > epsilon else {
187216
return
188217
}
189218

@@ -213,8 +242,6 @@ open class RichTextViewController: UIViewController, NSLayoutManagerDelegate, UI
213242
return
214243
}
215244

216-
layoutElementsOnTextView(containerSize: textView.bounds.size)
217-
218245
expectedTextViewSizeAfterOrientationChange = nil
219246
}
220247

@@ -274,37 +301,39 @@ open class RichTextViewController: UIViewController, NSLayoutManagerDelegate, UI
274301
- contentInset.left
275302
- contentInset.right
276303

277-
let scaleFactor = newWidth > 0 && attrView.frame.width > 0 ? newWidth / attrView.frame.width : max(attrView.frame.width, newWidth)
278-
let newHeight = scaleFactor * attrView.frame.height
304+
// Compute the origin inside the text container using glyph location and line fragment rect
305+
let fragmentOriginX = floor(lineFragmentRect.minX + glyphLocation.x)
306+
let fragmentOriginY = floor(lineFragmentRect.minY + glyphLocation.y)
279307

280-
// Rect specifying an area where text should not be rendered.
308+
// Rect specifying an area where text should not be rendered (in text container coordinates).
281309
// The rect is being updated right before the exclusion path is created.
282310
var boundingRect = CGRect(
283-
x: lineFragmentRect.minX + glyphLocation.x + contentInset.left - 1,
284-
y: lineFragmentRect.minY + glyphLocation.y + contentInset.top,
285-
width: containerSize.width,
286-
height: newHeight
311+
x: fragmentOriginX,
312+
y: fragmentOriginY + contentInset.top,
313+
width: newWidth,
314+
height: 0 // does not matter now as will ask to calculate height below in layout method
287315
)
288316

289-
// Rect specifying an area where the attachment is rendered. This can differ from the `boundingRect`.
290-
let attachmentRect = CGRect(
291-
x: lineFragmentRect.minX + glyphLocation.x + contentInset.left - 1,
292-
y: lineFragmentRect.minY + glyphLocation.y + contentInset.top,
317+
// Rect specifying an area where the attachment is rendered (in text view coordinates).
318+
// Must add content inset to convert from text container coords to text view coords.
319+
var attachmentRect = CGRect(
320+
x: contentInset.left + fragmentOriginX,
321+
y: contentInset.top + fragmentOriginY,
293322
width: newWidth,
294-
height: newHeight
323+
height: 0 // does not matter now as will ask to calculate height below in layout method
295324
)
296-
325+
297326
if attrView.superview == nil {
298-
attrView.frame = attachmentRect
299-
300327
attachmentCastedView.layout(with: attachmentRect.width)
301328

302329
let exclusionKey = String(range.hashValue) + Constant.embedSuffix
303330

304331
// Update bounding rect after laying out the view.
305332
let updatedRect = attachmentCastedView.frame
306-
boundingRect.size.height = updatedRect.height
307-
333+
boundingRect.size.height = floor(updatedRect.height)
334+
attachmentRect.size.height = floor(updatedRect.height)
335+
attrView.frame = attachmentRect
336+
308337
addExclusionPath(for: boundingRect, key: exclusionKey)
309338

310339
textView.addSubview(attrView)

0 commit comments

Comments
 (0)