From c1ef98e36267c021734ac9d233a5f6a5a391f1b4 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Sat, 21 Dec 2024 13:06:14 -1000 Subject: [PATCH 1/6] Update text view gesture recognizer to use textkit 2 --- Simplenote.xcodeproj/project.pbxproj | 8 ++ .../xcshareddata/swiftpm/Package.resolved | 96 ------------------- .../UIGestureRecognizer+Simplenote.swift | 14 ++- Simplenote/NSTextLayoutFragment.swift | 17 ++++ .../NSTextLayoutManager+Simplenote.swift | 17 ++++ 5 files changed, 53 insertions(+), 99 deletions(-) delete mode 100644 Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Simplenote/NSTextLayoutFragment.swift create mode 100644 Simplenote/NSTextLayoutManager+Simplenote.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index d74bfac6e..605d06472 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -541,6 +541,8 @@ 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 */; }; + BABB22E12D162E6D00FCF47D /* NSTextLayoutManager+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABB22E02D162E6200FCF47D /* NSTextLayoutManager+Simplenote.swift */; }; + BABB22E32D162E8400FCF47D /* NSTextLayoutFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABB22E22D162E7D00FCF47D /* NSTextLayoutFragment.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 */; }; @@ -1244,6 +1246,8 @@ BAB6C04626BA4CAF007495C4 /* WidgetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetController.swift; sourceTree = ""; }; BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteIntentHandler.swift; sourceTree = ""; }; BABB22DE2D14DA6300FCF47D /* SPTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPTextView.swift; sourceTree = ""; }; + BABB22E02D162E6200FCF47D /* NSTextLayoutManager+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextLayoutManager+Simplenote.swift"; sourceTree = ""; }; + BABB22E22D162E7D00FCF47D /* NSTextLayoutFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTextLayoutFragment.swift; sourceTree = ""; }; BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = ""; }; BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNoteIntentHandler.swift; sourceTree = ""; }; BAE08625261282D1009D40CD /* Note+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Publish.swift"; sourceTree = ""; }; @@ -1879,6 +1883,8 @@ E21F57B817C1244E001F02D3 /* SPEditorTextView.m */, A6BBDA45255034E6005C8343 /* SPEditorTextView+Simplenote.swift */, B52E2B142537480A0074509A /* SPEditorTapRecognizerDelegate.swift */, + BABB22E02D162E6200FCF47D /* NSTextLayoutManager+Simplenote.swift */, + BABB22E22D162E7D00FCF47D /* NSTextLayoutFragment.swift */, ); name = Editor; sourceTree = ""; @@ -3471,6 +3477,7 @@ A60DF30825A44F0F00FDADF3 /* PinLockRemoveController.swift in Sources */, A694ABAB25D1549D00CC3A2D /* FileStorage.swift in Sources */, B513FB2422EF6A4B00B178AC /* SPUserInterface.swift in Sources */, + BABB22E32D162E8400FCF47D /* NSTextLayoutFragment.swift in Sources */, B5DF734022A54EE800602CE7 /* SPDefaultTableViewCell.swift in Sources */, B5796549250684EC00DFD6F7 /* EditorFactory.swift in Sources */, A6C2721825AF0C1E00593731 /* TagListViewCell.swift in Sources */, @@ -3542,6 +3549,7 @@ A6CDF900256B9CB900CF2F27 /* ViewSpinner.swift in Sources */, 375D24B721E01131007AB25A /* stack.c in Sources */, 373AD30821C4739500A4EA89 /* NSMutableAttributedString+Styling.m in Sources */, + BABB22E12D162E6D00FCF47D /* NSTextLayoutManager+Simplenote.swift in Sources */, B524AE112352CC7900EA11D4 /* UIScreen+Simplenote.swift in Sources */, A68A4345256FEE3000D1CA5D /* SPSettingsViewController+Extensions.swift in Sources */, B543C7E523CF775C00003A80 /* NSSortDescriptor+Simplenote.swift in Sources */, diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 16c53eec3..000000000 --- a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,96 +0,0 @@ -{ - "originHash" : "5c7dc5a8c3fa899620d4a2168cf121438ee740cb5c2b5fac978ee6263951dbe7", - "pins" : [ - { - "identity" : "automattic-tracks-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Automattic/Automattic-Tracks-iOS", - "state" : { - "revision" : "4d7d7138a9f2b36c3fd4618aa488ed9e8de2f726", - "version" : "3.5.0" - } - }, - { - "identity" : "screenobject", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Automattic/ScreenObject", - "state" : { - "revision" : "328db56c62aab91440ec5e07cc9f7eef6e26a26e", - "version" : "0.2.3" - } - }, - { - "identity" : "sentry-cocoa", - "kind" : "remoteSourceControl", - "location" : "https://github.com/getsentry/sentry-cocoa", - "state" : { - "revision" : "5421f94cc859eb65f5ae3866165a053aa634431e", - "version" : "8.32.0" - } - }, - { - "identity" : "simplenoteendpoints-swift", - "kind" : "remoteSourceControl", - "location" : "git@github.com:Automattic/SimplenoteEndpoints-Swift.git", - "state" : { - "revision" : "3d1c0a5db39ca798a7de10e7faeef8ae66e941e6", - "version" : "1.0.0" - } - }, - { - "identity" : "simplenotefoundation-swift", - "kind" : "remoteSourceControl", - "location" : "git@github.com:Automattic/SimplenoteFoundation-Swift.git", - "state" : { - "revision" : "2731e4d28c42d394ddc62cd864d9f7ff96759228", - "version" : "1.3.0" - } - }, - { - "identity" : "simplenoteinterlinks-swift", - "kind" : "remoteSourceControl", - "location" : "git@github.com:Automattic/SimplenoteInterlinks-Swift.git", - "state" : { - "revision" : "be3827a5bf05c5349ed62126c3e1dc60a2a6cee6", - "version" : "1.1.0" - } - }, - { - "identity" : "simplenotesearch-swift", - "kind" : "remoteSourceControl", - "location" : "git@github.com:Automattic/SimplenoteSearch-Swift.git", - "state" : { - "revision" : "499d2809d169fcbeb9ff75568d9f1f937f290ffc", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-sodium", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jedisct1/swift-sodium", - "state" : { - "revision" : "4f9164a0a2c9a6a7ff53a2833d54a5c79c957342", - "version" : "0.9.1" - } - }, - { - "identity" : "uideviceidentifier", - "kind" : "remoteSourceControl", - "location" : "https://github.com/squarefrog/UIDeviceIdentifier", - "state" : { - "revision" : "4699794b08bb79a4d77785edaba6ea739e298e4b", - "version" : "2.3.0" - } - }, - { - "identity" : "xcuitesthelpers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Automattic/XCUITestHelpers", - "state" : { - "revision" : "5179cb69d58b90761cc713bdee7740c4889d3295", - "version" : "0.4.0" - } - } - ], - "version" : 3 -} diff --git a/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift b/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift index dbcd32bd5..d9c0cd853 100644 --- a/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift +++ b/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift @@ -21,8 +21,16 @@ extension UIGestureRecognizer { locationInContainer.x -= textView.textContainerInset.left locationInContainer.y -= textView.textContainerInset.top - return textView.layoutManager.characterIndex(for: locationInContainer, - in: textView.textContainer, - fractionOfDistanceBetweenInsertionPoints: nil) + guard #available(iOS 17.0, *), + let textLayoutManager = textView.textLayoutManager else { + // TextKit 1 fallback + return textView.layoutManager.characterIndex( + for: locationInContainer, + in: textView.textContainer, + fractionOfDistanceBetweenInsertionPoints: nil) + } + + // TextKit 2 + return textLayoutManager.characterIndex(for: locationInContainer) ?? .zero } } diff --git a/Simplenote/NSTextLayoutFragment.swift b/Simplenote/NSTextLayoutFragment.swift new file mode 100644 index 000000000..b9187db1d --- /dev/null +++ b/Simplenote/NSTextLayoutFragment.swift @@ -0,0 +1,17 @@ +// +// NSTextLayoutFragment.swift +// Simplenote +// +// Created by Charlie Scheer on 12/20/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +extension NSTextLayoutFragment { + @available(iOS 17.0, *) + func characterIndex(for location: CGPoint) -> Int? { + guard let lineFragment = textLineFragment(forVerticalOffset: location.y, requiresExactMatch: true) else { + return nil + } + return lineFragment.characterIndex(for: location) + } +} diff --git a/Simplenote/NSTextLayoutManager+Simplenote.swift b/Simplenote/NSTextLayoutManager+Simplenote.swift new file mode 100644 index 000000000..7350b4bf7 --- /dev/null +++ b/Simplenote/NSTextLayoutManager+Simplenote.swift @@ -0,0 +1,17 @@ +// +// NSTextLayoutManager+Simplenote.swift +// Simplenote +// +// Created by Charlie Scheer on 12/20/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +extension NSTextLayoutManager { + @available(iOS 17.0, *) + func characterIndex(for location: CGPoint) -> Int? { + guard let lineFragment = textLayoutFragment(for: location) else { + return nil + } + return lineFragment.characterIndex(for: location) + } +} From abbe73d5787d925b5baf2a4e588392839b4c2afe Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Sat, 21 Dec 2024 13:26:12 -1000 Subject: [PATCH 2/6] Ensure textkit 1 still works on older devices --- Simplenote/Classes/SPTextView.m | 6 +----- Simplenote/SPTextView.swift | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/SPTextView.m b/Simplenote/Classes/SPTextView.m index f7777054b..aa9175a95 100644 --- a/Simplenote/Classes/SPTextView.m +++ b/Simplenote/Classes/SPTextView.m @@ -20,14 +20,10 @@ @implementation SPTextView - (instancetype)init { - - SPInteractiveTextStorage *textStorage = [[SPInteractiveTextStorage alloc] init]; - - NSTextContainer *container = [self setupTextContainerWith:textStorage]; + NSTextContainer *container = [self setupTextContainer]; self = [super initWithFrame:CGRectZero textContainer:container]; if (self) { - self.interactiveTextStorage = textStorage; /* Issue #188: diff --git a/Simplenote/SPTextView.swift b/Simplenote/SPTextView.swift index a34315b24..854c68f04 100644 --- a/Simplenote/SPTextView.swift +++ b/Simplenote/SPTextView.swift @@ -8,11 +8,13 @@ extension SPTextView { @objc - func setupTextContainer(with textStorage: SPInteractiveTextStorage) -> NSTextContainer { + func setupTextContainer() -> NSTextContainer { let container = NSTextContainer(size: .zero) container.widthTracksTextView = true container.heightTracksTextView = true + let textStorage = SPInteractiveTextStorage() + interactiveTextStorage = textStorage if #available(iOS 16.0, *) { let textLayoutManager = NSTextLayoutManager() @@ -22,12 +24,30 @@ extension SPTextView { textLayoutManager.textContainer = container } else { + let layoutManager = NSLayoutManager() layoutManager.addTextContainer(container) textStorage.addLayoutManager(layoutManager) } return container } + + private func makeContainer() -> NSTextContainer { + var container: NSTextContainer + if #available(iOS 17.0, *) { + let fallbackContainer = SPFallbackTextContainer(size: .zero) + + // This value is set incase we need to fall back to text kit 1 + // + fallbackContainer.textView = self + + container = fallbackContainer + } else { + container = NSTextContainer() + } + + return container + } } // MARK: NSTextContentStorageDelegate From 534d52e308ab99f549417f4c19a1ff96019f0a42 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 26 Dec 2024 10:49:24 -1000 Subject: [PATCH 3/6] Restored package.resolved file --- .../xcshareddata/swiftpm/Package.resolved | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..16c53eec3 --- /dev/null +++ b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,96 @@ +{ + "originHash" : "5c7dc5a8c3fa899620d4a2168cf121438ee740cb5c2b5fac978ee6263951dbe7", + "pins" : [ + { + "identity" : "automattic-tracks-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Automattic/Automattic-Tracks-iOS", + "state" : { + "revision" : "4d7d7138a9f2b36c3fd4618aa488ed9e8de2f726", + "version" : "3.5.0" + } + }, + { + "identity" : "screenobject", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Automattic/ScreenObject", + "state" : { + "revision" : "328db56c62aab91440ec5e07cc9f7eef6e26a26e", + "version" : "0.2.3" + } + }, + { + "identity" : "sentry-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/getsentry/sentry-cocoa", + "state" : { + "revision" : "5421f94cc859eb65f5ae3866165a053aa634431e", + "version" : "8.32.0" + } + }, + { + "identity" : "simplenoteendpoints-swift", + "kind" : "remoteSourceControl", + "location" : "git@github.com:Automattic/SimplenoteEndpoints-Swift.git", + "state" : { + "revision" : "3d1c0a5db39ca798a7de10e7faeef8ae66e941e6", + "version" : "1.0.0" + } + }, + { + "identity" : "simplenotefoundation-swift", + "kind" : "remoteSourceControl", + "location" : "git@github.com:Automattic/SimplenoteFoundation-Swift.git", + "state" : { + "revision" : "2731e4d28c42d394ddc62cd864d9f7ff96759228", + "version" : "1.3.0" + } + }, + { + "identity" : "simplenoteinterlinks-swift", + "kind" : "remoteSourceControl", + "location" : "git@github.com:Automattic/SimplenoteInterlinks-Swift.git", + "state" : { + "revision" : "be3827a5bf05c5349ed62126c3e1dc60a2a6cee6", + "version" : "1.1.0" + } + }, + { + "identity" : "simplenotesearch-swift", + "kind" : "remoteSourceControl", + "location" : "git@github.com:Automattic/SimplenoteSearch-Swift.git", + "state" : { + "revision" : "499d2809d169fcbeb9ff75568d9f1f937f290ffc", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-sodium", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jedisct1/swift-sodium", + "state" : { + "revision" : "4f9164a0a2c9a6a7ff53a2833d54a5c79c957342", + "version" : "0.9.1" + } + }, + { + "identity" : "uideviceidentifier", + "kind" : "remoteSourceControl", + "location" : "https://github.com/squarefrog/UIDeviceIdentifier", + "state" : { + "revision" : "4699794b08bb79a4d77785edaba6ea739e298e4b", + "version" : "2.3.0" + } + }, + { + "identity" : "xcuitesthelpers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Automattic/XCUITestHelpers", + "state" : { + "revision" : "5179cb69d58b90761cc713bdee7740c4889d3295", + "version" : "0.4.0" + } + } + ], + "version" : 3 +} From 41753760cd2792e7f37ccd89088fa96229cfcc0f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 26 Dec 2024 10:50:38 -1000 Subject: [PATCH 4/6] Removed unused makeContainer method --- Simplenote/SPTextView.swift | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Simplenote/SPTextView.swift b/Simplenote/SPTextView.swift index 854c68f04..ee3bdb9d9 100644 --- a/Simplenote/SPTextView.swift +++ b/Simplenote/SPTextView.swift @@ -31,23 +31,6 @@ extension SPTextView { return container } - - private func makeContainer() -> NSTextContainer { - var container: NSTextContainer - if #available(iOS 17.0, *) { - let fallbackContainer = SPFallbackTextContainer(size: .zero) - - // This value is set incase we need to fall back to text kit 1 - // - fallbackContainer.textView = self - - container = fallbackContainer - } else { - container = NSTextContainer() - } - - return container - } } // MARK: NSTextContentStorageDelegate From fa0f31bd01b8a459ffdafc72d72206522782319d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 26 Dec 2024 11:16:46 -1000 Subject: [PATCH 5/6] Fixes issue where editing a new note wont apply header style --- Simplenote/SPTextView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPTextView.swift b/Simplenote/SPTextView.swift index ee3bdb9d9..fc1c525e5 100644 --- a/Simplenote/SPTextView.swift +++ b/Simplenote/SPTextView.swift @@ -37,7 +37,7 @@ extension SPTextView { // extension SPTextView: NSTextContentStorageDelegate { public func textContentStorage(_ textContentStorage: NSTextContentStorage, textParagraphWith range: NSRange) -> NSTextParagraph? { - guard let originalText = textContentStorage.textStorage?.attributedSubstring(from: range) as? NSMutableAttributedString else { + guard let originalText = textContentStorage.textStorage?.attributedSubstring(from: range).mutableCopy() as? NSMutableAttributedString else { return nil } From 2fde51efcb320a3e84455ba8b0f08fdf4676e7b4 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 27 Dec 2024 14:15:58 -1000 Subject: [PATCH 6/6] Move initialization of spTextView text storage out of setupTextContainer --- Simplenote/Classes/SPTextView.m | 5 +++-- Simplenote/SPTextView.swift | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/SPTextView.m b/Simplenote/Classes/SPTextView.m index aa9175a95..bc874bed9 100644 --- a/Simplenote/Classes/SPTextView.m +++ b/Simplenote/Classes/SPTextView.m @@ -20,11 +20,12 @@ @implementation SPTextView - (instancetype)init { - NSTextContainer *container = [self setupTextContainer]; + SPInteractiveTextStorage *textStorage = [[SPInteractiveTextStorage alloc] init]; + NSTextContainer *container = [self setupTextContainerWith:textStorage]; self = [super initWithFrame:CGRectZero textContainer:container]; if (self) { - + self.interactiveTextStorage = textStorage; /* Issue #188: =========== diff --git a/Simplenote/SPTextView.swift b/Simplenote/SPTextView.swift index fc1c525e5..70f3d01c2 100644 --- a/Simplenote/SPTextView.swift +++ b/Simplenote/SPTextView.swift @@ -8,14 +8,11 @@ extension SPTextView { @objc - func setupTextContainer() -> NSTextContainer { + func setupTextContainer(with textStorage: SPInteractiveTextStorage) -> NSTextContainer { let container = NSTextContainer(size: .zero) container.widthTracksTextView = true container.heightTracksTextView = true - let textStorage = SPInteractiveTextStorage() - interactiveTextStorage = textStorage - if #available(iOS 16.0, *) { let textLayoutManager = NSTextLayoutManager() let contentStorage = NSTextContentStorage()