Skip to content

Commit 09a3f21

Browse files
Add Example App (#64)
### Description - Adds an example app. Just a barebones example of how to use the `TextView` class to create a plain-text editor. - The project is set up so the package is a local, editable, dependency. This has been extremely useful in CESE, and should help when developing this package. It's also good documentation, and we can point people towards this example app if they'd like to see how to use the text view. ### Related Issues - N/A ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots ![Screenshot 2025-01-09 at 3 06 14 PM](https://github.com/user-attachments/assets/bc94e85d-eada-4dd0-9337-133cd79ede9e)
1 parent 41ea36f commit 09a3f21

File tree

13 files changed

+746
-0
lines changed

13 files changed

+746
-0
lines changed

Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.pbxproj

+408
Large diffs are not rendered by default.

Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"pins" : [
3+
{
4+
"identity" : "rearrange",
5+
"kind" : "remoteSourceControl",
6+
"location" : "https://github.com/ChimeHQ/Rearrange",
7+
"state" : {
8+
"revision" : "5ff7f3363f7a08f77e0d761e38e6add31c2136e1",
9+
"version" : "1.8.1"
10+
}
11+
},
12+
{
13+
"identity" : "swift-collections",
14+
"kind" : "remoteSourceControl",
15+
"location" : "https://github.com/apple/swift-collections.git",
16+
"state" : {
17+
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
18+
"version" : "1.1.4"
19+
}
20+
},
21+
{
22+
"identity" : "swiftlintplugin",
23+
"kind" : "remoteSourceControl",
24+
"location" : "https://github.com/lukepistrol/SwiftLintPlugin",
25+
"state" : {
26+
"revision" : "87454f5c9ff4d644086aec2a0df1ffba678e7f3c",
27+
"version" : "0.57.1"
28+
}
29+
},
30+
{
31+
"identity" : "textstory",
32+
"kind" : "remoteSourceControl",
33+
"location" : "https://github.com/ChimeHQ/TextStory",
34+
"state" : {
35+
"revision" : "8dc9148b46fcf93b08ea9d4ef9bdb5e4f700e008",
36+
"version" : "0.9.0"
37+
}
38+
}
39+
],
40+
"version" : 2
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
},
8+
{
9+
"appearances" : [
10+
{
11+
"appearance" : "luminosity",
12+
"value" : "dark"
13+
}
14+
],
15+
"idiom" : "universal",
16+
"platform" : "ios",
17+
"size" : "1024x1024"
18+
},
19+
{
20+
"appearances" : [
21+
{
22+
"appearance" : "luminosity",
23+
"value" : "tinted"
24+
}
25+
],
26+
"idiom" : "universal",
27+
"platform" : "ios",
28+
"size" : "1024x1024"
29+
}
30+
],
31+
"info" : {
32+
"author" : "xcode",
33+
"version" : 1
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
<key>com.apple.security.files.user-selected.read-write</key>
8+
<true/>
9+
</dict>
10+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// CodeEditTextViewExampleApp.swift
3+
// CodeEditTextViewExample
4+
//
5+
// Created by Khan Winter on 1/9/25.
6+
//
7+
8+
import SwiftUI
9+
10+
@main
11+
struct CodeEditTextViewExampleApp: App {
12+
var body: some Scene {
13+
DocumentGroup(newDocument: CodeEditTextViewExampleDocument()) { file in
14+
ContentView(document: file.$document)
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// CodeEditTextViewExampleDocument.swift
3+
// CodeEditTextViewExample
4+
//
5+
// Created by Khan Winter on 1/9/25.
6+
//
7+
8+
import SwiftUI
9+
import UniformTypeIdentifiers
10+
11+
struct CodeEditTextViewExampleDocument: FileDocument {
12+
var text: String
13+
14+
init(text: String = "") {
15+
self.text = text
16+
}
17+
18+
static var readableContentTypes: [UTType] {
19+
[
20+
.item
21+
]
22+
}
23+
24+
init(configuration: ReadConfiguration) throws {
25+
guard let data = configuration.file.regularFileContents else {
26+
throw CocoaError(.fileReadCorruptFile)
27+
}
28+
text = String(decoding: data, as: UTF8.self)
29+
}
30+
31+
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
32+
let data = Data(text.utf8)
33+
return .init(regularFileWithContents: data)
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDocumentTypes</key>
6+
<array>
7+
<dict>
8+
<key>CFBundleTypeRole</key>
9+
<string>Editor</string>
10+
<key>LSItemContentTypes</key>
11+
<array>
12+
<string>com.example.plain-text</string>
13+
</array>
14+
<key>NSUbiquitousDocumentUserActivityType</key>
15+
<string>$(PRODUCT_BUNDLE_IDENTIFIER).exampledocument</string>
16+
</dict>
17+
</array>
18+
<key>UTImportedTypeDeclarations</key>
19+
<array>
20+
<dict>
21+
<key>UTTypeConformsTo</key>
22+
<array>
23+
<string>public.plain-text</string>
24+
</array>
25+
<key>UTTypeDescription</key>
26+
<string>Example Text</string>
27+
<key>UTTypeIdentifier</key>
28+
<string>com.example.plain-text</string>
29+
<key>UTTypeTagSpecification</key>
30+
<dict>
31+
<key>public.filename-extension</key>
32+
<array>
33+
<string>exampletext</string>
34+
</array>
35+
</dict>
36+
</dict>
37+
</array>
38+
</dict>
39+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// ContentView.swift
3+
// CodeEditTextViewExample
4+
//
5+
// Created by Khan Winter on 1/9/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ContentView: View {
11+
@Binding var document: CodeEditTextViewExampleDocument
12+
13+
var body: some View {
14+
SwiftUITextView(text: $document.text)
15+
}
16+
}
17+
18+
#Preview {
19+
ContentView(document: .constant(CodeEditTextViewExampleDocument()))
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// SwiftUITextView.swift
3+
// CodeEditTextViewExample
4+
//
5+
// Created by Khan Winter on 1/9/25.
6+
//
7+
8+
import SwiftUI
9+
import AppKit
10+
import CodeEditTextView
11+
12+
struct SwiftUITextView: NSViewControllerRepresentable {
13+
@Binding var text: String
14+
15+
func makeNSViewController(context: Context) -> TextViewController {
16+
let controller = TextViewController(string: text)
17+
context.coordinator.controller = controller
18+
return controller
19+
}
20+
21+
func updateNSViewController(_ nsViewController: TextViewController, context: Context) {
22+
// Do nothing, our binding has to be a one-way binding
23+
}
24+
25+
func makeCoordinator() -> Coordinator {
26+
Coordinator(text: $text)
27+
}
28+
29+
@MainActor
30+
public class Coordinator: NSObject {
31+
weak var controller: TextViewController?
32+
var text: Binding<String>
33+
34+
init(text: Binding<String>) {
35+
self.text = text
36+
super.init()
37+
38+
NotificationCenter.default.addObserver(
39+
self,
40+
selector: #selector(textViewDidChangeText(_:)),
41+
name: TextView.textDidChangeNotification,
42+
object: nil
43+
)
44+
}
45+
46+
@objc func textViewDidChangeText(_ notification: Notification) {
47+
guard let textView = notification.object as? TextView,
48+
let controller,
49+
controller.textView === textView else {
50+
return
51+
}
52+
text.wrappedValue = textView.string
53+
}
54+
55+
deinit {
56+
NotificationCenter.default.removeObserver(self)
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//
2+
// TextViewController.swift
3+
// CodeEditTextViewExample
4+
//
5+
// Created by Khan Winter on 1/9/25.
6+
//
7+
8+
import AppKit
9+
import CodeEditTextView
10+
11+
class TextViewController: NSViewController {
12+
var scrollView: NSScrollView!
13+
var textView: TextView!
14+
15+
init(string: String) {
16+
textView = TextView(string: string)
17+
super.init(nibName: nil, bundle: nil)
18+
}
19+
20+
required init?(coder: NSCoder) {
21+
fatalError("init(coder:) has not been implemented")
22+
}
23+
24+
override func loadView() {
25+
scrollView = NSScrollView()
26+
textView.translatesAutoresizingMaskIntoConstraints = false
27+
28+
scrollView.translatesAutoresizingMaskIntoConstraints = false
29+
scrollView.documentView = textView
30+
scrollView.contentView.postsFrameChangedNotifications = true
31+
scrollView.contentView.postsBoundsChangedNotifications = true
32+
scrollView.hasVerticalScroller = true
33+
34+
self.view = scrollView
35+
36+
NSLayoutConstraint.activate([
37+
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
38+
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
39+
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
40+
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
41+
])
42+
43+
// Layout on scroll change
44+
NotificationCenter.default.addObserver(
45+
forName: NSView.frameDidChangeNotification,
46+
object: scrollView.contentView,
47+
queue: .main
48+
) { [weak self] _ in
49+
self?.textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
50+
}
51+
52+
textView.updateFrameIfNeeded()
53+
}
54+
55+
deinit {
56+
NotificationCenter.default.removeObserver(self)
57+
}
58+
}

0 commit comments

Comments
 (0)