Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TextKit 2 - Implement basic TK2 class and graceful fallback to TK1 #1690

Merged
merged 7 commits into from
Dec 26, 2024
4 changes: 4 additions & 0 deletions Simplenote.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@
BAB6C04526BA4A04007495C4 /* String+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5476BC023D8E5D0000E7723 /* String+Simplenote.swift */; };
BAB6C04726BA4CAF007495C4 /* WidgetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB6C04626BA4CAF007495C4 /* WidgetController.swift */; };
BAB898D32BEC404200E238B8 /* CreateNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */; };
BABB22DF2D14DA6600FCF47D /* SPTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABB22DE2D14DA6300FCF47D /* SPTextView.swift */; };
BABFFF2226CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; };
BABFFF2326CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; };
BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; };
Expand Down Expand Up @@ -1242,6 +1243,7 @@
BAB576BD2670512C00B0C56F /* NoteWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWidget.swift; sourceTree = "<group>"; };
BAB6C04626BA4CAF007495C4 /* WidgetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetController.swift; sourceTree = "<group>"; };
BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteIntentHandler.swift; sourceTree = "<group>"; };
BABB22DE2D14DA6300FCF47D /* SPTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPTextView.swift; sourceTree = "<group>"; };
BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = "<group>"; };
BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNoteIntentHandler.swift; sourceTree = "<group>"; };
BAE08625261282D1009D40CD /* Note+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Publish.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1872,6 +1874,7 @@
children = (
B5E3F3972539F1E900271AEA /* SPTextView.h */,
B5E3F3962539F1E900271AEA /* SPTextView.m */,
BABB22DE2D14DA6300FCF47D /* SPTextView.swift */,
E21F57B717C1244E001F02D3 /* SPEditorTextView.h */,
E21F57B817C1244E001F02D3 /* SPEditorTextView.m */,
A6BBDA45255034E6005C8343 /* SPEditorTextView+Simplenote.swift */,
Expand Down Expand Up @@ -3562,6 +3565,7 @@
B53C5A5A230330CD00DA2143 /* SPNoteListViewController+Extensions.swift in Sources */,
A6C0DFB525C1581D00B9BE39 /* UIScrollView+Simplenote.swift in Sources */,
375D24B621E01131007AB25A /* html_blocks.c in Sources */,
BABB22DF2D14DA6600FCF47D /* SPTextView.swift in Sources */,
46A3C98217DFA81A002865AE /* NSString+Attributed.m in Sources */,
375D24BA21E01131007AB25A /* document.c in Sources */,
BA6DA19126DB5F1B000464C8 /* URLComponents.swift in Sources */,
Expand Down
9 changes: 2 additions & 7 deletions Simplenote/Classes/SPTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,9 @@ @implementation SPTextView
- (instancetype)init {

SPInteractiveTextStorage *textStorage = [[SPInteractiveTextStorage alloc] init];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];

NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(0, CGFLOAT_MAX)];
container.widthTracksTextView = YES;
container.heightTracksTextView = YES;
[layoutManager addTextContainer:container];
[textStorage addLayoutManager:layoutManager];

NSTextContainer *container = [self setupTextContainerWith:textStorage];

self = [super initWithFrame:CGRectZero textContainer:container];
if (self) {
self.interactiveTextStorage = textStorage;
Expand Down
95 changes: 95 additions & 0 deletions Simplenote/SPTextView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// SPTextView.swift
// Simplenote
//
// Created by Charlie Scheer on 12/19/24.
// Copyright © 2024 Automattic. All rights reserved.
//

extension SPTextView {
@objc
func setupTextContainer(with textStorage: SPInteractiveTextStorage) -> NSTextContainer {
let container = NSTextContainer(size: .zero)
container.widthTracksTextView = true
container.heightTracksTextView = true


if #available(iOS 16.0, *) {
let textLayoutManager = NSTextLayoutManager()
let contentStorage = NSTextContentStorage()
contentStorage.delegate = self
contentStorage.addTextLayoutManager(textLayoutManager)
textLayoutManager.textContainer = container

} else {
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note: layoutManager may not be init'ed by the time it's called here

textStorage.addLayoutManager(layoutManager)
}

return container
}
}

// MARK: NSTextContentStorageDelegate
//
extension SPTextView: NSTextContentStorageDelegate {
public func textContentStorage(_ textContentStorage: NSTextContentStorage, textParagraphWith range: NSRange) -> NSTextParagraph? {
guard let originalText = textContentStorage.textStorage?.attributedSubstring(from: range) as? NSMutableAttributedString else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing .mutabelCopy() here

return nil
}

let style = textInRangeIsHeader(range) ? headlineStyle : defaultStyle
originalText.addAttributes(style, range: originalText.fullRange)

return NSTextParagraph(attributedString: originalText)
}

func textInRangeIsHeader(_ range: NSRange) -> Bool {
range.location == .zero
}

// MARK: Styles
//
var headlineFont: UIFont {
UIFont.preferredFont(for: .title1, weight: .bold)
}

var defaultFont: UIFont {
UIFont.preferredFont(forTextStyle: .body)
}

var defaultTextColor: UIColor {
UIColor.simplenoteNoteHeadlineColor
}

var lineSpacing: CGFloat {
defaultFont.lineHeight * Metrics.lineSpacingMultipler
}

var defaultStyle: [NSAttributedString.Key: Any] {
[
.font: defaultFont,
.foregroundColor: defaultTextColor,
.paragraphStyle: NSMutableParagraphStyle(lineSpacing: lineSpacing)
]
}

var headlineStyle: [NSAttributedString.Key: Any] {
[
.font: headlineFont,
.foregroundColor: defaultTextColor,
]
}
}

// MARK: - Metrics
//
private enum Metrics {
static let lineSpacingMultiplerPad: CGFloat = 0.40
static let lineSpacingMultiplerPhone: CGFloat = 0.20

static var lineSpacingMultipler: CGFloat {
UIDevice.isPad ? lineSpacingMultiplerPad : lineSpacingMultiplerPhone
}
}