Skip to content

Commit 92a41f8

Browse files
committed
Add MarkupFormattingTextView
1 parent c6ec243 commit 92a41f8

29 files changed

Lines changed: 479 additions & 655 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.5.0] - 2021-06-21
4+
5+
### Added
6+
7+
- `MarkupFormattingTextView` for displaying and editing text with the formatting determined by a `ParsedAttributedString`
8+
39
## [0.4.1] - 2021-06-20 Oops
410

511
I left old versions of files in the 0.4.0 release. Clean that up.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55

66
let package = Package(
77
name: "TextMarkupKit",
8-
platforms: [.iOS(.v13)],
8+
platforms: [.iOS(.v14)],
99
products: [
1010
// Products define the executables and libraries a package produces, and make them visible to other packages.
1111
.library(

Sources/TextMarkupKit/AttributesArray.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419
import Logging

Sources/TextMarkupKit/BufferProtocols.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import Logging
19+
import MobileCoreServices
20+
import ObjectiveCTextStorageWrapper
21+
import os
22+
import UIKit
23+
import UniformTypeIdentifiers
24+
25+
private let log = OSLog(subsystem: "org.brians-brain.TextMarkupKit", category: "MarkupFormattingTextView")
26+
27+
private extension Logging.Logger {
28+
static let textView: Logging.Logger = {
29+
var logger = Logger(label: "org.brians-brain.TextMarkupKit")
30+
logger.logLevel = .info
31+
return logger
32+
}()
33+
}
34+
35+
/// A protocol that the text views use to store images on paste
36+
public protocol MarkupFormattingTextViewImageStorage {
37+
/// Store image data.
38+
/// - parameter imageData: The image data to store
39+
/// - parameter suffix: Image data suffix that identifies the data format (e.g., "jpeg", "png")
40+
/// - parameter key: Optional pre-defined key to use for this image.
41+
/// - returns: A string that represents this image in the markup language.
42+
func storeImageData(_ imageData: Data, type: UTType, key: String?) throws -> String
43+
}
44+
45+
/// A UITextView subclass that uses a `ParsedAttributedString` for text storage and formatting.
46+
public final class MarkupFormattingTextView: UITextView {
47+
/// Creates a `MarkupFormattingTextView` that uses `parsedAttributedString` as its textStorage.
48+
///
49+
/// - Parameter parsedAttributedString: The `ParsedAttributedString` to use for text storage and formatting.
50+
/// - Parameter layoutManager: Optional custom NSLayoutManager to use.
51+
public init(
52+
parsedAttributedString: ParsedAttributedString,
53+
layoutManager: NSLayoutManager = NSLayoutManager()
54+
) {
55+
self.parsedAttributedString = parsedAttributedString
56+
self.storage = ObjectiveCTextStorageWrapper(storage: parsedAttributedString)
57+
let layoutManager = layoutManager
58+
storage.addLayoutManager(layoutManager)
59+
let textContainer = NSTextContainer()
60+
layoutManager.addTextContainer(textContainer)
61+
super.init(frame: .zero, textContainer: textContainer)
62+
pasteConfiguration = UIPasteConfiguration(
63+
acceptableTypeIdentifiers: [
64+
kUTTypeJPEG as String,
65+
kUTTypePNG as String,
66+
kUTTypeImage as String,
67+
kUTTypePlainText as String,
68+
]
69+
)
70+
}
71+
72+
/// An object that can store pasted image data.
73+
public var imageStorage: MarkupFormattingTextViewImageStorage?
74+
75+
/// The `ParsedAttributedString` used for text storage and formatting.
76+
public let parsedAttributedString: ParsedAttributedString
77+
78+
/// A private wrapper around `parsedAttributedString` for efficient interaction with TextKit.
79+
private let storage: ObjectiveCTextStorageWrapper
80+
81+
@available(*, unavailable)
82+
required init?(coder: NSCoder) {
83+
fatalError("init(coder:) has not been implemented")
84+
}
85+
86+
override public func copy(_ sender: Any?) {
87+
guard let textStorage = textStorage as? ObjectiveCTextStorageWrapper, let parsedAttributedString = textStorage.storage as? ParsedAttributedString else {
88+
Logger.textView.error("Expected to get a ParsedAttributedString")
89+
return
90+
}
91+
let rawTextRange = parsedAttributedString.rawStringRange(forRange: selectedRange)
92+
let characters = parsedAttributedString.rawString[rawTextRange]
93+
UIPasteboard.general.string = String(utf16CodeUnits: characters, count: characters.count)
94+
}
95+
96+
override public func canPaste(_ itemProviders: [NSItemProvider]) -> Bool {
97+
Logger.textView.info("Determining if we can paste from \(itemProviders)")
98+
let typeIdentifiers = pasteConfiguration!.acceptableTypeIdentifiers
99+
for itemProvider in itemProviders {
100+
for typeIdentifier in typeIdentifiers where itemProvider.hasItemConformingToTypeIdentifier(typeIdentifier) {
101+
Logger.textView.info("Item provider has type \(typeIdentifier) so we can paste")
102+
return true
103+
}
104+
}
105+
return false
106+
}
107+
108+
override public func paste(itemProviders: [NSItemProvider]) {
109+
Logger.textView.info("Pasting \(itemProviders)")
110+
super.paste(itemProviders: itemProviders)
111+
}
112+
113+
override public func paste(_ sender: Any?) {
114+
if let image = UIPasteboard.general.image, let imageStorage = self.imageStorage {
115+
Logger.textView.info("Pasting an image")
116+
let imageKey: String?
117+
if let jpegData = UIPasteboard.general.data(forPasteboardType: UTType.jpeg.identifier) {
118+
Logger.textView.info("Got JPEG data = \(jpegData.count) bytes")
119+
imageKey = try? imageStorage.storeImageData(jpegData, type: .jpeg, key: nil)
120+
} else if let pngData = UIPasteboard.general.data(forPasteboardType: UTType.png.identifier) {
121+
Logger.textView.info("Got PNG data = \(pngData.count) bytes")
122+
imageKey = try? imageStorage.storeImageData(pngData, type: .png, key: nil)
123+
} else if let convertedData = image.jpegData(compressionQuality: 0.8) {
124+
Logger.textView.info("Did JPEG conversion ourselves = \(convertedData.count) bytes")
125+
imageKey = try? imageStorage.storeImageData(convertedData, type: .jpeg, key: nil)
126+
} else {
127+
Logger.textView.error("Could not get image data")
128+
imageKey = nil
129+
}
130+
if let imageKey = imageKey {
131+
textStorage.replaceCharacters(in: selectedRange, with: imageKey)
132+
}
133+
} else {
134+
Logger.textView.info("Using superclass to paste text")
135+
super.paste(sender)
136+
}
137+
}
138+
139+
override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
140+
if action == #selector(paste(_:)), UIPasteboard.general.image != nil {
141+
Logger.textView.info("There's an image on the pasteboard, so allow pasting")
142+
return true
143+
}
144+
return super.canPerformAction(action, withSender: sender)
145+
}
146+
147+
override public func insertText(_ text: String) {
148+
os_signpost(.begin, log: log, name: "keystroke")
149+
super.insertText(text)
150+
os_signpost(.end, log: log, name: "keystroke")
151+
}
152+
}

Sources/TextMarkupKit/MemoizationTable.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419
import os

Sources/TextMarkupKit/MiniMarkdownGrammar.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419

Sources/TextMarkupKit/NSAttributedString+Attributes.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Logging
419
import UIKit

Sources/TextMarkupKit/ParsedAttributedString.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419
import Logging

Sources/TextMarkupKit/ParsedString.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
// Copyright (c) 2018-2021 Brian Dewey. Covered by the Apache 2.0 license.
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
217

318
import Foundation
419

0 commit comments

Comments
 (0)