Skip to content
This repository was archived by the owner on Oct 30, 2024. It is now read-only.

Overhaul text rendering #1055

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Slide for Reddit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
09F48EBC20F652CF00BAC8AC /* VideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F48EBB20F652CF00BAC8AC /* VideoView.swift */; };
2D3972CE071AEB2AC2811512 /* Pods_Slide_for_RedditTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 733616027CB406046CABF77A /* Pods_Slide_for_RedditTests.framework */; };
323EFB2821434781005157FA /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 323EFB2721434781005157FA /* ProgressBarView.swift */; };
B4BF517224D4EA23000000D9 /* CoolTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4BF517124D4EA23000000D9 /* CoolTextView.swift */; };
B776B17D5CA92860424F37C0 /* ModQueueContributionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B776BA7C2B7217C3875F3BEC /* ModQueueContributionLoader.swift */; };
B776B1DF80711B443ED76B61 /* SettingsPro.swift in Sources */ = {isa = PBXBuildFile; fileRef = B776B2AEE4A2438F5803329C /* SettingsPro.swift */; };
B776B1E23F4DEC18069F1579 /* MediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B776BC65995FE8D1EE75E2AB /* MediaViewController.swift */; };
Expand Down Expand Up @@ -455,6 +456,7 @@
5A3C22D1F67B7A388DDF34E6 /* Pods-Slide for Reddit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Slide for Reddit.release.xcconfig"; path = "Pods/Target Support Files/Pods-Slide for Reddit/Pods-Slide for Reddit.release.xcconfig"; sourceTree = "<group>"; };
733616027CB406046CABF77A /* Pods_Slide_for_RedditTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Slide_for_RedditTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B12412FF705B2BEB9D3B245F /* Pods_Slide_for_Reddit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Slide_for_Reddit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B4BF517124D4EA23000000D9 /* CoolTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoolTextView.swift; sourceTree = "<group>"; };
B776B21CFB79FA354C296037 /* ColorMuxPagingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorMuxPagingViewController.swift; sourceTree = "<group>"; };
B776B2AEE4A2438F5803329C /* SettingsPro.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPro.swift; sourceTree = "<group>"; };
B776B2E54F7438959A76E42E /* LiveThreadUpdate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveThreadUpdate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1302,6 +1304,34 @@
BE15FE742163D03900288D86 /* TopLockViewController.swift */,
BECF52432012AF7A00A310F9 /* VCPresenter.swift */,
BE11351B2159390700502C5B /* WatchSessionManager.swift */,
BE17A24F215FFFAA002C6CE6 /* ReadLaterContributionLoader.swift */,
BE17A2512160127C002C6CE6 /* ReadLaterViewController.swift */,
BE15FE742163D03900288D86 /* TopLockViewController.swift */,
BE8264E82194AE0E002A540E /* OfflineOverviewViewController.swift */,
BE25402521CCC03E003BD547 /* ForceTouchGestureRecognizer.swift */,
BE8AE94221F9225000E1C0D1 /* Main.storyboard */,
BE8AE94621F922F400E1C0D1 /* ColorPickerViewController.swift */,
BE47AF70225C574700A61FB0 /* ManageMultireddit.swift */,
BE762C9422A2FF7200B29757 /* DraftFindReturnViewController.swift */,
BE602AF9229F3A5200AF0DC3 /* LinkBubble.swift */,
BE7A2D3D22AD99600005F74F /* DragDownAlertMenu.swift */,
BEC4A1A3232E98CF00EE5114 /* ProfileInfoViewController.swift */,
BEC4A1B1232F3B4F00EE5114 /* collections.plist */,
BEC4A1B3232F3F1800EE5114 /* CollectionsContributionLoader.swift */,
BEC4A1B5232F3F9A00EE5114 /* CollectionsViewController.swift */,
BEC4A1B72331533400EE5114 /* AutoplayScrollViewHandler.swift */,
BEB7BAD923A05F2500E39593 /* InsetTransitioningDelegate.swift */,
BEC814EB24BD43EE005C8E8C /* SiriShortcuts.swift */,
BEC814ED24BE8B94005C8E8C /* HistoryViewController.swift */,
BEC814EF24BE8BBD005C8E8C /* HistoryContributionLoader.swift */,
BE25CAD324C3A28300736CA5 /* SwipeForwardNavigationController.swift */,
BE25CAD524C3A65400736CA5 /* SwipeForwardAnimatedTransitioning.swift */,
BE25CAD924C4B02700736CA5 /* NavigationHomeViewController.swift */,
BE25CADB24C4E3DA00736CA5 /* MainViewController.swift */,
BE25CADD24C4E3FB00736CA5 /* SplitMainViewController.swift */,
BE93DECE24CE4D8300464B64 /* icons.plist */,
BE93DED024CE550800464B64 /* subcolors.plist */,
B4BF517124D4EA23000000D9 /* CoolTextView.swift */,
);
path = "Slide for Reddit";
sourceTree = "<group>";
Expand Down Expand Up @@ -2154,6 +2184,7 @@
09526EF8214C399F005A3F6B /* UIPanGestureRecognizer+Utilities.swift in Sources */,
B776B69E386227A51C8E10DF /* MediaTableViewController.swift in Sources */,
B776B1E23F4DEC18069F1579 /* MediaViewController.swift in Sources */,
B4BF517224D4EA23000000D9 /* CoolTextView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
87 changes: 56 additions & 31 deletions Slide for Reddit/CommentDepthCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ class CommentDepthCell: MarginedTableViewCell, UIViewControllerPreviewingDelegat
$0.isUserInteractionEnabled = true
$0.accessibilityIdentifier = "Comment body"
$0.ignoreHeight = true
$0.firstTextView.textContainerInset = UIEdgeInsets(top: 3, left: 0, bottom: 0, right: 0)
// TODOjon:
// $0.firstTextView.textContainerInset = UIEdgeInsets(top: 3, left: 0, bottom: 0, right: 0)
})

self.title = YYLabel().then({
Expand Down Expand Up @@ -2342,8 +2343,8 @@ extension CommentDepthCell: UIContextMenuInteractionDelegate {
} else if self.commentBody.overflow.frame.contains(location) {
let innerLocation = self.commentBody.convert(location, to: self.commentBody.overflow)
for view in self.commentBody.overflow.subviews {
if view.frame.contains(innerLocation) && view is YYLabel {
return UITargetedPreview(view: self.commentBody, parameters: self.getLocationForPreviewedText(view as! YYLabel, innerLocation, self.previewedURL?.absoluteString, self.commentBody) ?? parameters)
if let view = view as? CoolTextView, view.frame.contains(innerLocation) {
return UITargetedPreview(view: self.commentBody, parameters: self.getLocationForPreviewedText(view, innerLocation, self.previewedURL?.absoluteString, self.commentBody) ?? parameters)
}
}
}
Expand All @@ -2362,8 +2363,8 @@ extension CommentDepthCell: UIContextMenuInteractionDelegate {
} else if self.commentBody.overflow.frame.contains(location) {
let innerLocation = self.commentBody.convert(location, to: self.commentBody.overflow)
for view in self.commentBody.overflow.subviews {
if view.frame.contains(innerLocation) && view is YYLabel {
return getConfigurationForTextView(view as! YYLabel, innerLocation)
if let view = view as? CoolTextView, view.frame.contains(innerLocation) {
return getConfigurationForTextView(view, innerLocation)
}
}
} else if self.commentBody.links.frame.contains(location) {
Expand All @@ -2380,44 +2381,34 @@ extension CommentDepthCell: UIContextMenuInteractionDelegate {
self.previewedVC = nil
}

func getLocationForPreviewedText(_ label: YYLabel, _ location: CGPoint, _ inputURL: String?, _ changeRectTo: UIView? = nil) -> UIPreviewParameters? {
func getLocationForPreviewedText(_ textView: CoolTextView, _ location: CGPoint, _ inputURL: String?, _ changeRectTo: UIView? = nil) -> UIPreviewParameters? {
if inputURL == nil {
return nil
}
let point = label.superview?.convert(location, to: label) ?? location
// TODOjon:
let point = textView.superview?.convert(location, to: textView) ?? location // Convert touch point from frame space to label space
var params: UIPreviewParameters?
if let attributedText = label.attributedText, let layoutManager = YYTextLayout(containerSize: label.frame.size, text: attributedText) {
let locationFinal = layoutManager.textPosition(for: point, lineIndex: layoutManager.lineIndex(for: point))
if locationFinal < 1000000 {
attributedText.enumerateAttribute(
.link,
in: NSRange(location: 0, length: attributedText.length)
) { (value, range, _) in
if let url = value as? NSURL {
if url.absoluteString == inputURL! {
let baseRects = layoutManager.selectionRects(for: YYTextRange(range: range))
var cgs = [NSValue]()
for rect in baseRects {
if changeRectTo != nil {
cgs.append(NSValue(cgRect: changeRectTo!.convert(rect.rect, from: label)))
} else {
cgs.append(NSValue(cgRect: rect.rect))
}
}
params = UIPreviewParameters(textLineRects: cgs)
params?.backgroundColor = .clear
}
if let attributedText = textView.attributedText {
attributedText.enumerateAttribute(
.link,
in: NSRange(location: 0, length: attributedText.length)
) { (value, range, _) in
if let url = value as? NSURL {
if url.absoluteString == inputURL! {
let rects = textView.selectionRects(for: range.toTextRange(textInput: textView)!).map {NSValue(cgRect: $0.rect)}
params = UIPreviewParameters(textLineRects: rects)
params?.backgroundColor = .clear
}
}
}
}
return params
}

func getConfigurationForTextView(_ label: YYLabel, _ location: CGPoint) -> UIContextMenuConfiguration? {
let point = label.superview?.convert(location, to: label) ?? location
func getConfigurationForTextView(_ textView: CoolTextView, _ location: CGPoint) -> UIContextMenuConfiguration? {
// let point = textView.superview?.convert(location, to: label) ?? location

if let attributedText = label.attributedText, let layoutManager = YYTextLayout(containerSize: label.frame.size, text: attributedText) {
if let attributedText = textView.attributedText, let layoutManager = YYTextLayout(containerSize: textView.frame.size, text: attributedText) {
let locationFinal = layoutManager.textPosition(for: point, lineIndex: layoutManager.lineIndex(for: point))
if locationFinal < 1000000 {
let attributes = attributedText.attributes(at: Int(locationFinal), effectiveRange: nil)
Expand Down Expand Up @@ -2547,3 +2538,37 @@ extension CommentDepthCell: UIContextMenuInteractionDelegate {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: nil)
}
}

extension UILabel {
func boundingRect(forCharacterRange range: NSRange) -> CGRect? {

guard let attributedText = attributedText else { return nil }

let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()

textStorage.addLayoutManager(layoutManager)

let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0.0

layoutManager.addTextContainer(textContainer)

var glyphRange = NSRange()

// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)

return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}

extension NSRange {
func toTextRange(textInput: UITextInput) -> UITextRange? {
if let rangeStart = textInput.position(from: textInput.beginningOfDocument, offset: location),
let rangeEnd = textInput.position(from: rangeStart, offset: length) {
return textInput.textRange(from: rangeStart, to: rangeEnd)
}
return nil
}
}
21 changes: 21 additions & 0 deletions Slide for Reddit/CoolTextView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// CoolTextView.swift
// Slide for Reddit
//
// Created by Jonathan Cole on 7/31/20.
// Copyright © 2020 Haptic Apps. All rights reserved.
//

import UIKit

class CoolTextView: UITextView {

/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/

}
14 changes: 8 additions & 6 deletions Slide for Reddit/LinkCellView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3186,8 +3186,8 @@ extension LinkCellView: UIContextMenuInteractionDelegate {
} else if self.textView.overflow.frame.contains(location) {
let innerLocation = self.textView.convert(location, to: self.textView.overflow)
for view in self.textView.overflow.subviews {
if view.frame.contains(innerLocation) && view is YYLabel {
return UITargetedPreview(view: self.textView, parameters: self.getLocationForPreviewedText(view as! YYLabel, innerLocation, self.previewedURL?.absoluteString, self.textView) ?? parameters)
if let view = view as? CoolTextView, view.frame.contains(innerLocation) {
return UITargetedPreview(view: self.textView, parameters: self.getLocationForPreviewedText(view, innerLocation, self.previewedURL?.absoluteString, self.textView) ?? parameters)
}
}
}
Expand All @@ -3203,7 +3203,8 @@ extension LinkCellView: UIContextMenuInteractionDelegate {
}
}

func getLocationForPreviewedText(_ label: YYLabel, _ location: CGPoint, _ inputURL: String?, _ changeRectTo: UIView? = nil) -> UIPreviewParameters? {
func getLocationForPreviewedText(_ label: CoolTextView, _ location: CGPoint, _ inputURL: String?, _ changeRectTo: UIView? = nil) -> UIPreviewParameters? {
// TODOjon:
if inputURL == nil {
return nil
}
Expand Down Expand Up @@ -3247,8 +3248,8 @@ extension LinkCellView: UIContextMenuInteractionDelegate {
let innerLocation = self.contentView.convert(innerPoint, to: self.textView.overflow)
print(innerLocation)
for view in self.textView.overflow.subviews {
if view.frame.contains(innerLocation) && view is YYLabel {
return getConfigurationForTextView(view as! YYLabel, innerLocation)
if let view = view as? CoolTextView, view.frame.contains(innerLocation) {
return getConfigurationForTextView(view, innerLocation)
}
}
}
Expand All @@ -3268,7 +3269,8 @@ extension LinkCellView: UIContextMenuInteractionDelegate {
return nil
}

func getConfigurationForTextView(_ label: YYLabel, _ location: CGPoint) -> UIContextMenuConfiguration? {
func getConfigurationForTextView(_ label: CoolTextView, _ location: CGPoint) -> UIContextMenuConfiguration? {
// TODOjon:
let point = label.superview?.convert(location, to: label) ?? location
if let attributedText = label.attributedText, let layoutManager = YYTextLayout(containerSize: label.frame.size, text: attributedText) {
let locationFinal = layoutManager.textPosition(for: point, lineIndex: layoutManager.lineIndex(for: point))
Expand Down
Loading