├── .github
└── FUNDING.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── TextEdit
│ ├── CarretView.swift
│ ├── GlyphsView.swift
│ ├── KeyboardViewController.swift
│ └── TextEdit.swift
└── TextEditSample
├── App.swift
├── Assets.xcassets
├── AppIcon.appiconset
│ └── Contents.json
└── Contents.json
├── Base.lproj
└── LaunchScreen.storyboard
├── Info.plist
├── SceneDelegate.swift
├── TextEdit.entitlements
├── TextEditSample.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── TextEditingView.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [krzyzanowskim]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | # SPM
13 | Package.resolved
14 |
15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
16 | build/
17 | DerivedData/
18 | *.moved-aside
19 | *.pbxuser
20 | !default.pbxuser
21 | *.mode1v3
22 | !default.mode1v3
23 | *.mode2v3
24 | !default.mode2v3
25 | *.perspectivev3
26 | !default.perspectivev3
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 |
31 | ## App packaging
32 | *.ipa
33 | *.dSYM.zip
34 | *.dSYM
35 |
36 | ## Playgrounds
37 | timeline.xctimeline
38 | playground.xcworkspace
39 |
40 | # Swift Package Manager
41 | #
42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
43 | # Packages/
44 | # Package.pins
45 | # Package.resolved
46 | # *.xcodeproj
47 | #
48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
49 | # hence it is not needed unless you have added a package configuration file to your project
50 | # .swiftpm
51 |
52 | .build/
53 |
54 | # CocoaPods
55 | #
56 | # We recommend against adding the Pods directory to your .gitignore. However
57 | # you should judge for yourself, the pros and cons are mentioned at:
58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
59 | #
60 | # Pods/
61 | #
62 | # Add this line if you want to avoid checking in source code from the Xcode workspace
63 | # *.xcworkspace
64 |
65 | # Carthage
66 | #
67 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
68 | # Carthage/Checkouts
69 |
70 | Carthage/Build/
71 |
72 | # Accio dependency management
73 | Dependencies/
74 | .accio/
75 |
76 | # fastlane
77 | #
78 | # It is recommended to not store the screenshots in the git repo.
79 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
80 | # For more information about the recommended setup visit:
81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
82 |
83 | fastlane/report.xml
84 | fastlane/Preview.html
85 | fastlane/screenshots/**/*.png
86 | fastlane/test_output
87 |
88 | # Code Injection
89 | #
90 | # After new code Injection tools there's a generated folder /iOSInjectionProject
91 | # https://github.com/johnno1962/injectionforxcode
92 |
93 | iOSInjectionProject/
94 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, Marcin Krzyzanowski
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "TextEdit",
7 | platforms: [.macOS(.v10_15), .iOS(.v14)],
8 | products: [
9 | .library(
10 | name: "TextEdit",
11 | targets: ["TextEdit"]),
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/krzyzanowskim/CoreTextSwift.git", from: "0.0.1"),
15 | ],
16 | targets: [
17 | .target(
18 | name: "TextEdit",
19 | dependencies: ["CoreTextSwift"])
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # SwiftUI TextEdit View
3 |
4 | A proof-of-concept text edit component in SwiftUI & CoreText. No UIKit, No AppKit, no UITextView/NSTextView/UITextField involved.
5 |
6 | *Note* Due to SwiftUI limitations (as of May 2021) it's not possible to handle keystrokes just with SwiftUI. To overcome this limitation, the `UIKeyboardViewController` is responsible for handling keys and forward to SwiftUI codebase.
7 |
8 | If you have questions or want to reach to me, use this thread: https://twitter.com/krzyzanowskim/status/1269402396217745410
9 |
10 | ## Authors
11 |
12 | [Marcin Krzyzanowski](http://krzyzanowskim.com)
13 | [@krzyzanowskim](https://twitter.com/krzyzanowskim)
14 |
15 |
16 | ## Screenshots
17 |
18 | 
19 |
20 |
21 |
22 | ## Usage/Examples
23 |
24 | ```swift
25 | struct TextEditingView: View {
26 | @State private var text = "type here...\n"
27 | @State private var font = UIFont.preferredFont(forTextStyle: .body) as CTFont
28 | @State private var carretWidth = 2.0 as CGFloat
29 |
30 | var body: some View {
31 | TextEdit(
32 | text: $text,
33 | font: $font,
34 | carretWidth: $carretWidth
35 | )
36 | }
37 | }
38 | ```
39 |
40 |
41 | ## FAQ
42 |
43 | #### How?
44 |
45 | CoreText + SwiftUI.
46 |
47 | #### Why?
48 |
49 | For fun and profit.
50 |
51 |
52 | ## Related
53 |
54 | Here are some related projects
55 |
56 | [CoreTextSwift](https://github.com/krzyzanowskim/CoreTextSwift)
57 |
--------------------------------------------------------------------------------
/Sources/TextEdit/CarretView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct CarretView: View {
4 | @Binding var width: CGFloat
5 |
6 | var body: some View {
7 | CarretShape()
8 | .stroke(lineWidth: width)
9 | .frame(width: width)
10 | .foregroundColor(Color.accentColor)
11 | }
12 | }
13 |
14 | struct CarretShape: Shape {
15 | func path(in rect: CGRect) -> Path {
16 | var path = Path()
17 | path.move(to: .zero)
18 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
19 | return path
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/TextEdit/GlyphsView.swift:
--------------------------------------------------------------------------------
1 | import CoreText
2 | import CoreTextSwift
3 | import SwiftUI
4 |
5 | // View because it not a single shape (colors and other things is not a single shape)
6 | struct GlyphsView: View {
7 | var attributedString: CFAttributedString
8 | var textFrame: CTFrame?
9 | private let invertY: CGFloat = -1 // invert for macOS
10 |
11 | init(_ attributedString: CFAttributedString, _ textFrame: CTFrame?) {
12 | self.attributedString = attributedString
13 | self.textFrame = textFrame
14 | }
15 |
16 | var body: some View {
17 | guard let textFrame = textFrame else {
18 | return Path()
19 | }
20 |
21 | var path = Path()
22 | let textFrameBox = textFrame.path().boundingBoxOfPath
23 |
24 | // transform to top-left coordinates
25 | let lineOrigins = textFrame.lineOrigins()
26 | .map { linePoint -> CGPoint in
27 | CGPoint(x: linePoint.x, y: textFrameBox.maxY - linePoint.y)
28 | }
29 |
30 | // draw all lines
31 | for (i, line) in textFrame.lines().enumerated() {
32 | let lineOrigin = lineOrigins[i]
33 | for glyphRun in line.glyphRuns() {
34 | let font = glyphRun.font
35 | let glyphs = glyphRun.glyphs()
36 | let glyphsPositions = glyphRun.glyphPositions()
37 | for (idx, glyph) in glyphs.enumerated() {
38 | let positionTransform = CGAffineTransform(translationX: glyphsPositions[idx].x, y: (invertY * glyphsPositions[idx].y) + lineOrigin.y)
39 | .scaledBy(x: 1, y: 1 * invertY)
40 |
41 | // path is nil for space
42 | if let glyphCGPath = font.path(for: glyph, transform: positionTransform) {
43 | path.addPath(Path(glyphCGPath))
44 | }
45 | }
46 | }
47 | }
48 |
49 | return path
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/TextEdit/KeyboardViewController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | import UIKit
4 |
5 | extension UIResponder {
6 | static let pressPressesBegan = NSNotification.Name("OMOMUIPressPressesBeganNotification")
7 | static let pressPressesEnded = NSNotification.Name("OMOMUIPressPressesEndedNotification")
8 | }
9 |
10 | private class UIKeyboardViewController: UIViewController {
11 | override func pressesBegan(_ presses: Set, with _: UIPressesEvent?) {
12 | NotificationCenter.default.post(name: UIResponder.pressPressesBegan, object: presses)
13 | }
14 |
15 | override func pressesEnded(_ presses: Set, with _: UIPressesEvent?) {
16 | NotificationCenter.default.post(name: UIResponder.pressPressesEnded, object: presses)
17 | }
18 |
19 | override func pressesChanged(_ presses: Set, with _: UIPressesEvent?) {
20 | print("pressesChanged \(presses)")
21 | }
22 |
23 | override func pressesCancelled(_ presses: Set, with _: UIPressesEvent?) {
24 | print("pressesCancelled \(presses)")
25 | }
26 | }
27 |
28 | struct KeyboardView: UIViewControllerRepresentable {
29 | func makeUIViewController(context _: Context) -> UIViewController {
30 | UIKeyboardViewController()
31 | }
32 |
33 | func updateUIViewController(_: UIViewController, context _: Context) {}
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/TextEdit/TextEdit.swift:
--------------------------------------------------------------------------------
1 | import CoreText
2 | import CoreTextSwift
3 | import SwiftUI
4 |
5 | // TODO:
6 | // - carret position expressed in characterIndex, instead CGPoint
7 | // - update (visually) carret possition as soon as index change
8 | // - insert text at the carret position
9 | // - selection
10 |
11 | public struct TextEdit: View {
12 | private struct DragState: Equatable {
13 | var location: CGPoint = .zero
14 | var lastLineHeight: CGFloat = .zero
15 |
16 | static let zero = DragState()
17 | }
18 |
19 | private struct CarretState: Equatable {
20 | var location: CGPoint = .zero
21 | var height: CGFloat = .zero
22 | }
23 |
24 | @Binding public var text: String
25 | @Binding public var font: CTFont
26 | @Binding public var carretWidth: CGFloat
27 |
28 | // Text frame is calculated in MyPreferenceViewSetter
29 | // then propagated to ancestors. Has to be that way
30 | // because we need width at that point
31 | @State private var textFrame: CTFrame? // cached here
32 | @State private var textFrameBox: CGRect? // cached boundingBoxOfPath
33 | @State private var lineOrigins: [CGPoint] = [] // cached
34 |
35 | @State private var carret = CarretState()
36 | @GestureState private var drag: DragState = .zero
37 |
38 | public init(text: Binding, font: Binding, carretWidth: Binding) {
39 | self._text = text
40 | self._font = font
41 | self._carretWidth = carretWidth
42 | }
43 |
44 | private var attributedString: CFAttributedString {
45 | CFAttributedStringCreate(nil, text as CFString, [NSAttributedString.Key.font.rawValue: font] as CFDictionary)!
46 | }
47 |
48 | private func line(at location: CGPoint) -> (idx: Int, origin: CGPoint, descent: CGFloat, line: CTLine)? {
49 | // calculate line height
50 | guard let textFrame = textFrame,
51 | let textFrameBox = textFrameBox,
52 | lineOrigins.isEmpty == false
53 | else {
54 | return nil
55 | }
56 |
57 | // adjust coordinates
58 | let lineOrigins = self.lineOrigins.map { linePoint -> CGPoint in
59 | CGPoint(x: linePoint.x, y: textFrameBox.maxY - linePoint.y)
60 | }
61 |
62 | // find the line. origins come with height only (for our rect layout)
63 | var prevY: CGFloat = 0
64 | for (lineIdx, lineOrigin) in lineOrigins.enumerated() {
65 | let line = textFrame.lines()[lineIdx]
66 | let (_, descent, _) = line.typographicBounds()
67 | if location.y > prevY, location.y <= lineOrigin.y + descent {
68 | // lineIdx is the line number we found!
69 | return (idx: lineIdx, origin: lineOrigin, descent: descent, line: line)
70 | }
71 | prevY = lineOrigin.y + descent
72 | }
73 |
74 | return nil
75 | }
76 |
77 | private func lineTypographicHeight(at location: CGPoint) -> CGFloat {
78 | line(at: location)?.line.typographicHeight() ?? .zero
79 | }
80 |
81 | // adjust position to always be "in line"
82 | private func lineAdjustedCarretPosition(at location: CGPoint) -> CGPoint {
83 | guard let line = line(at: location) else {
84 | return location
85 | }
86 |
87 | // find character index at location
88 | let characterIdx = line.line.characterIndex(forPosition: location)
89 | let offsetForCharacter = line.line.offsetForCharacterIndex(characterIdx)
90 |
91 | let (_, descent, _) = line.line.typographicBounds()
92 | return CGPoint(x: offsetForCharacter, y: line.origin.y + descent - line.line.typographicHeight())
93 | }
94 |
95 | public var body: some View {
96 | KeyboardView().frame(width: 0, height: 0).background(Color.clear)
97 |
98 | GeometryReader { _ in
99 | GlyphsView(self.attributedString, self.textFrame)
100 | .simultaneousGesture(
101 | DragGesture().updating(self.$drag) { value, state, _ in
102 | state = DragState(location: self.lineAdjustedCarretPosition(at: value.location),
103 | lastLineHeight: self.lineTypographicHeight(at: value.location))
104 | }
105 | )
106 | .simultaneousGesture(
107 | DragGesture()
108 | .onEnded { _ in
109 | // Here! because GestureState value is still valid (yay) and can be read
110 | // It won't work from each gesture onEnded closure
111 | self.carret.location = self.drag.location
112 | self.carret.height = self.drag.lastLineHeight
113 | }
114 | )
115 | .background(
116 | MyPreferenceViewSetter(
117 | attributedString: self.attributedString
118 | )
119 | )
120 |
121 | // when dragging, use GestureState, then use State
122 | if self.drag == .zero {
123 | CarretView(width: self.$carretWidth)
124 | .frame(height: self.carret.height) // current height should be calculated per line
125 | .offset(x: self.carret.location.x, y: self.carret.location.y)
126 | } else {
127 | CarretView(width: self.$carretWidth)
128 | .frame(height: self.drag.lastLineHeight) // current height should be calculated per line
129 | .offset(x: self.drag.location.x, y: self.drag.location.y)
130 | }
131 | }
132 | .onPreferenceChange(TextPreferenceKey.self) { preferences in
133 | // get frame from/for GlyphsView.
134 | // funny enough it's called before GlyphsView body is called
135 | if let textFrame = preferences.first?.textFrame {
136 | self.textFrame = textFrame
137 | self.textFrameBox = textFrame.path().boundingBoxOfPath
138 | self.lineOrigins = textFrame.lineOrigins()
139 |
140 | // FIXME: not correct
141 | // self.carret = CarretState(location: .zero, height: self.font.ascent() + self.font.descent() + self.font.leading())
142 |
143 | // UPDATE CARRET HERE!
144 |
145 | // move carret + number of characters
146 | // adjust coordinates
147 | let lineOrigins = self.lineOrigins.map { linePoint -> CGPoint in
148 | CGPoint(x: linePoint.x, y: self.textFrameBox!.maxY - linePoint.y)
149 | }
150 |
151 | // find the line of last character
152 | // FIXME: there's no "before" in empty string
153 | let lastCharacterIndex: String.Index
154 | if self.text.isEmpty {
155 | lastCharacterIndex = self.text.startIndex
156 | } else {
157 | lastCharacterIndex = self.text.index(before: self.text.endIndex)
158 | }
159 |
160 | for (lineIdx, line) in textFrame.lines().enumerated() {
161 | if let lineRange = Range(line.stringRange(), in: self.text),
162 | lineRange.contains(lastCharacterIndex)
163 | {
164 | // get the X offset of the last character
165 | let q = NSRange(lineRange, in: self.text)
166 | let lineOffsetX = line.offsetForCharacterIndex(q.upperBound)
167 |
168 | let (_, descent, _) = line.typographicBounds()
169 | let pos = CGPoint(x: lineOrigins[lineIdx].x + lineOffsetX,
170 | y: lineOrigins[lineIdx].y + descent - line.typographicHeight())
171 |
172 | // update carret
173 | // TODO: use lense here
174 | self.carret = CarretState(location: pos, height: self.font.ascent() + self.font.descent() + self.font.leading())
175 | }
176 | }
177 | }
178 | }
179 | .onReceive(NotificationCenter.default.publisher(for: UIResponder.pressPressesBegan), perform: { notification in
180 | guard let presses = notification.object as? Set, let press = presses.first, let key = press.key else {
181 | return
182 | }
183 |
184 | switch key.keyCode {
185 | case .keyboardDeleteOrBackspace:
186 | if !self.text.isEmpty {
187 | self.text = String(self.text.dropLast())
188 | }
189 | case .keyboardReturnOrEnter, .keyboardReturn:
190 | // something wrong with new line
191 | // https://stackoverflow.com/questions/44683156/linecount-for-attributedstring-from-coretext-is-wrong
192 | // Maybe custom framesetter would help here
193 | self.text += "\n"
194 | default:
195 | self.text += key.characters
196 | }
197 | })
198 | }
199 | }
200 |
201 | struct MyPreferenceViewSetter: View {
202 | let attributedString: CFAttributedString
203 |
204 | var body: some View {
205 | GeometryReader { geometry in
206 | Rectangle()
207 | .fill(Color.clear)
208 | .preference(key: TextPreferenceKey.self,
209 | value: [TextPreferenceData(rect: geometry.frame(in: .local),
210 | // if you link against the new SDK and want to typeset text with a UTF-16 length longer than 4096,
211 | // you now need to pass in the new option `kCTTypesetterOptionAllowUnboundedLayout`
212 | textFrame: self.attributedString.framesetter().createFrame(geometry.frame(in: .local)),
213 | attributedString: self.attributedString)])
214 | }
215 | }
216 | }
217 |
218 | struct TextPreferenceData: Equatable {
219 | let rect: CGRect
220 | let textFrame: CTFrame
221 | let attributedString: CFAttributedString
222 | }
223 |
224 | struct TextPreferenceKey: PreferenceKey {
225 | typealias Value = [TextPreferenceData]
226 |
227 | static var defaultValue: [TextPreferenceData] = []
228 |
229 | static func reduce(value: inout [TextPreferenceData], nextValue: () -> [TextPreferenceData]) {
230 | value.append(contentsOf: nextValue())
231 | }
232 | }
233 |
234 | // Notes:
235 | //
236 | // http://unicode.org/faq/char_combmark.html
237 | // U+01B5 LATIN CAPITAL LETTER Z WITH STROKE
238 | // U+0327 COMBINING CEDILLA
239 | // U+0308 COMBINING DIAERESIS
240 | // "\u{01B5}\u{0327}\u{0308}" // Ƶ̧̈ <- broken here
241 | // "\u{0061}\u{0328}\u{0301}" // ą́ <- broken in Xcode
242 | // "\u{0105}\u{0301}" // ą́ <- ok
243 | // "Z\u{0308}"
244 |
--------------------------------------------------------------------------------
/TextEditSample/App.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TextEdit
4 | //
5 | // Created by Marcin Krzyzanowski on 31/05/2020.
6 | // Copyright © 2020 Marcin Krzyzanowski. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | @main
13 | struct TextEditSample: App {
14 | @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
15 |
16 | var body: some Scene {
17 | WindowGroup {
18 | TextEditingView()
19 | }
20 | }
21 | }
22 |
23 |
24 |
25 | class AppDelegate: UIResponder, UIApplicationDelegate {
26 | //
27 | }
28 |
--------------------------------------------------------------------------------
/TextEditSample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/TextEditSample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TextEditSample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/TextEditSample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/TextEditSample/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // TextEdit
4 | //
5 | // Created by Marcin Krzyzanowski on 31/05/2020.
6 | // Copyright © 2020 Marcin Krzyzanowski. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import UIKit
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 | var window: UIWindow?
14 |
15 | func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 |
20 | // Create the SwiftUI view that provides the window contents.
21 | let contentView = TextEditingView()
22 |
23 | // Use a UIHostingController as window root view controller.
24 | if let windowScene = scene as? UIWindowScene {
25 | let window = UIWindow(windowScene: windowScene)
26 | window.rootViewController = UIHostingController(rootView: contentView)
27 | self.window = window
28 | window.makeKeyAndVisible()
29 | }
30 | }
31 |
32 | func sceneDidDisconnect(_: UIScene) {
33 | // Called as the scene is being released by the system.
34 | // This occurs shortly after the scene enters the background, or when its session is discarded.
35 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
36 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
37 | }
38 |
39 | func sceneDidBecomeActive(_: UIScene) {
40 | // Called when the scene has moved from an inactive state to an active state.
41 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
42 | }
43 |
44 | func sceneWillResignActive(_: UIScene) {
45 | // Called when the scene will move from an active state to an inactive state.
46 | // This may occur due to temporary interruptions (ex. an incoming phone call).
47 | }
48 |
49 | func sceneWillEnterForeground(_: UIScene) {
50 | // Called as the scene transitions from the background to the foreground.
51 | // Use this method to undo the changes made on entering the background.
52 | }
53 |
54 | func sceneDidEnterBackground(_: UIScene) {
55 | // Called as the scene transitions from the foreground to the background.
56 | // Use this method to save data, release shared resources, and store enough scene-specific state information
57 | // to restore the scene back to its current state.
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/TextEditSample/TextEdit.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TextEditSample/TextEditSample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 7575AD4F264055BD007B28DE /* TextEdit in Frameworks */ = {isa = PBXBuildFile; productRef = 7575AD4E264055BD007B28DE /* TextEdit */; };
11 | 7575AD5126405664007B28DE /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7575AD5026405664007B28DE /* App.swift */; };
12 | 75EDB9A124840616007483FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 75EDB99F24840616007483FF /* LaunchScreen.storyboard */; };
13 | 75F96CD3264054FB00120ED2 /* TextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F96CD2264054FB00120ED2 /* TextEditingView.swift */; };
14 | 75F96CD52640550600120ED2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 75F96CD42640550600120ED2 /* Assets.xcassets */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 7575AD5026405664007B28DE /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
19 | 75DC27722640553F001C4252 /* TextEdit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = TextEdit; path = ..; sourceTree = ""; };
20 | 75EDB99124840614007483FF /* TextEditSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextEditSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | 75EDB9A024840616007483FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
22 | 75F96CD2264054FB00120ED2 /* TextEditingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEditingView.swift; sourceTree = ""; };
23 | 75F96CD42640550600120ED2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | 75EDB98E24840614007483FF /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | 7575AD4F264055BD007B28DE /* TextEdit in Frameworks */,
32 | );
33 | runOnlyForDeploymentPostprocessing = 0;
34 | };
35 | /* End PBXFrameworksBuildPhase section */
36 |
37 | /* Begin PBXGroup section */
38 | 75EDB98824840614007483FF = {
39 | isa = PBXGroup;
40 | children = (
41 | 75DC27722640553F001C4252 /* TextEdit */,
42 | 75F96CD42640550600120ED2 /* Assets.xcassets */,
43 | 7575AD5026405664007B28DE /* App.swift */,
44 | 75F96CD2264054FB00120ED2 /* TextEditingView.swift */,
45 | 75EDB99F24840616007483FF /* LaunchScreen.storyboard */,
46 | 75EDB99224840614007483FF /* Products */,
47 | 75EDB9B12484087F007483FF /* Frameworks */,
48 | );
49 | indentWidth = 4;
50 | sourceTree = "";
51 | tabWidth = 4;
52 | };
53 | 75EDB99224840614007483FF /* Products */ = {
54 | isa = PBXGroup;
55 | children = (
56 | 75EDB99124840614007483FF /* TextEditSample.app */,
57 | );
58 | name = Products;
59 | sourceTree = "";
60 | };
61 | 75EDB9B12484087F007483FF /* Frameworks */ = {
62 | isa = PBXGroup;
63 | children = (
64 | );
65 | name = Frameworks;
66 | sourceTree = "";
67 | };
68 | /* End PBXGroup section */
69 |
70 | /* Begin PBXNativeTarget section */
71 | 75EDB99024840614007483FF /* TextEditSample */ = {
72 | isa = PBXNativeTarget;
73 | buildConfigurationList = 75EDB9A524840616007483FF /* Build configuration list for PBXNativeTarget "TextEditSample" */;
74 | buildPhases = (
75 | 75EDB98D24840614007483FF /* Sources */,
76 | 75EDB98E24840614007483FF /* Frameworks */,
77 | 75EDB98F24840614007483FF /* Resources */,
78 | );
79 | buildRules = (
80 | );
81 | dependencies = (
82 | );
83 | name = TextEditSample;
84 | packageProductDependencies = (
85 | 7575AD4E264055BD007B28DE /* TextEdit */,
86 | );
87 | productName = TextEdit;
88 | productReference = 75EDB99124840614007483FF /* TextEditSample.app */;
89 | productType = "com.apple.product-type.application";
90 | };
91 | /* End PBXNativeTarget section */
92 |
93 | /* Begin PBXProject section */
94 | 75EDB98924840614007483FF /* Project object */ = {
95 | isa = PBXProject;
96 | attributes = {
97 | LastSwiftUpdateCheck = 1150;
98 | LastUpgradeCheck = 1250;
99 | ORGANIZATIONNAME = "Marcin Krzyzanowski";
100 | TargetAttributes = {
101 | 75EDB99024840614007483FF = {
102 | CreatedOnToolsVersion = 11.5;
103 | LastSwiftMigration = 1250;
104 | };
105 | };
106 | };
107 | buildConfigurationList = 75EDB98C24840614007483FF /* Build configuration list for PBXProject "TextEditSample" */;
108 | compatibilityVersion = "Xcode 9.3";
109 | developmentRegion = en;
110 | hasScannedForEncodings = 0;
111 | knownRegions = (
112 | en,
113 | Base,
114 | );
115 | mainGroup = 75EDB98824840614007483FF;
116 | packageReferences = (
117 | );
118 | productRefGroup = 75EDB99224840614007483FF /* Products */;
119 | projectDirPath = "";
120 | projectRoot = "";
121 | targets = (
122 | 75EDB99024840614007483FF /* TextEditSample */,
123 | );
124 | };
125 | /* End PBXProject section */
126 |
127 | /* Begin PBXResourcesBuildPhase section */
128 | 75EDB98F24840614007483FF /* Resources */ = {
129 | isa = PBXResourcesBuildPhase;
130 | buildActionMask = 2147483647;
131 | files = (
132 | 75F96CD52640550600120ED2 /* Assets.xcassets in Resources */,
133 | 75EDB9A124840616007483FF /* LaunchScreen.storyboard in Resources */,
134 | );
135 | runOnlyForDeploymentPostprocessing = 0;
136 | };
137 | /* End PBXResourcesBuildPhase section */
138 |
139 | /* Begin PBXSourcesBuildPhase section */
140 | 75EDB98D24840614007483FF /* Sources */ = {
141 | isa = PBXSourcesBuildPhase;
142 | buildActionMask = 2147483647;
143 | files = (
144 | 7575AD5126405664007B28DE /* App.swift in Sources */,
145 | 75F96CD3264054FB00120ED2 /* TextEditingView.swift in Sources */,
146 | );
147 | runOnlyForDeploymentPostprocessing = 0;
148 | };
149 | /* End PBXSourcesBuildPhase section */
150 |
151 | /* Begin PBXVariantGroup section */
152 | 75EDB99F24840616007483FF /* LaunchScreen.storyboard */ = {
153 | isa = PBXVariantGroup;
154 | children = (
155 | 75EDB9A024840616007483FF /* Base */,
156 | );
157 | name = LaunchScreen.storyboard;
158 | sourceTree = "";
159 | };
160 | /* End PBXVariantGroup section */
161 |
162 | /* Begin XCBuildConfiguration section */
163 | 75EDB9A324840616007483FF /* Debug */ = {
164 | isa = XCBuildConfiguration;
165 | buildSettings = {
166 | ALWAYS_SEARCH_USER_PATHS = NO;
167 | CLANG_ANALYZER_NONNULL = YES;
168 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
169 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
170 | CLANG_CXX_LIBRARY = "libc++";
171 | CLANG_ENABLE_MODULES = YES;
172 | CLANG_ENABLE_OBJC_ARC = YES;
173 | CLANG_ENABLE_OBJC_WEAK = YES;
174 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
175 | CLANG_WARN_BOOL_CONVERSION = YES;
176 | CLANG_WARN_COMMA = YES;
177 | CLANG_WARN_CONSTANT_CONVERSION = YES;
178 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
180 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
181 | CLANG_WARN_EMPTY_BODY = YES;
182 | CLANG_WARN_ENUM_CONVERSION = YES;
183 | CLANG_WARN_INFINITE_RECURSION = YES;
184 | CLANG_WARN_INT_CONVERSION = YES;
185 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
186 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
189 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
190 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
191 | CLANG_WARN_STRICT_PROTOTYPES = YES;
192 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
193 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
194 | CLANG_WARN_UNREACHABLE_CODE = YES;
195 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
196 | COPY_PHASE_STRIP = NO;
197 | DEBUG_INFORMATION_FORMAT = dwarf;
198 | ENABLE_STRICT_OBJC_MSGSEND = YES;
199 | ENABLE_TESTABILITY = YES;
200 | GCC_C_LANGUAGE_STANDARD = gnu11;
201 | GCC_DYNAMIC_NO_PIC = NO;
202 | GCC_NO_COMMON_BLOCKS = YES;
203 | GCC_OPTIMIZATION_LEVEL = 0;
204 | GCC_PREPROCESSOR_DEFINITIONS = (
205 | "DEBUG=1",
206 | "$(inherited)",
207 | );
208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
210 | GCC_WARN_UNDECLARED_SELECTOR = YES;
211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
212 | GCC_WARN_UNUSED_FUNCTION = YES;
213 | GCC_WARN_UNUSED_VARIABLE = YES;
214 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
215 | MTL_FAST_MATH = YES;
216 | ONLY_ACTIVE_ARCH = YES;
217 | SDKROOT = iphoneos;
218 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
220 | };
221 | name = Debug;
222 | };
223 | 75EDB9A424840616007483FF /* Release */ = {
224 | isa = XCBuildConfiguration;
225 | buildSettings = {
226 | ALWAYS_SEARCH_USER_PATHS = NO;
227 | CLANG_ANALYZER_NONNULL = YES;
228 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
230 | CLANG_CXX_LIBRARY = "libc++";
231 | CLANG_ENABLE_MODULES = YES;
232 | CLANG_ENABLE_OBJC_ARC = YES;
233 | CLANG_ENABLE_OBJC_WEAK = YES;
234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
235 | CLANG_WARN_BOOL_CONVERSION = YES;
236 | CLANG_WARN_COMMA = YES;
237 | CLANG_WARN_CONSTANT_CONVERSION = YES;
238 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
241 | CLANG_WARN_EMPTY_BODY = YES;
242 | CLANG_WARN_ENUM_CONVERSION = YES;
243 | CLANG_WARN_INFINITE_RECURSION = YES;
244 | CLANG_WARN_INT_CONVERSION = YES;
245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
246 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
249 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
251 | CLANG_WARN_STRICT_PROTOTYPES = YES;
252 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
254 | CLANG_WARN_UNREACHABLE_CODE = YES;
255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
256 | COPY_PHASE_STRIP = NO;
257 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
258 | ENABLE_NS_ASSERTIONS = NO;
259 | ENABLE_STRICT_OBJC_MSGSEND = YES;
260 | GCC_C_LANGUAGE_STANDARD = gnu11;
261 | GCC_NO_COMMON_BLOCKS = YES;
262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
264 | GCC_WARN_UNDECLARED_SELECTOR = YES;
265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
266 | GCC_WARN_UNUSED_FUNCTION = YES;
267 | GCC_WARN_UNUSED_VARIABLE = YES;
268 | MTL_ENABLE_DEBUG_INFO = NO;
269 | MTL_FAST_MATH = YES;
270 | SDKROOT = iphoneos;
271 | SWIFT_COMPILATION_MODE = wholemodule;
272 | SWIFT_OPTIMIZATION_LEVEL = "-O";
273 | VALIDATE_PRODUCT = YES;
274 | };
275 | name = Release;
276 | };
277 | 75EDB9A624840616007483FF /* Debug */ = {
278 | isa = XCBuildConfiguration;
279 | buildSettings = {
280 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
281 | CLANG_ENABLE_MODULES = YES;
282 | CODE_SIGN_ENTITLEMENTS = TextEdit.entitlements;
283 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
284 | CODE_SIGN_STYLE = Automatic;
285 | DEVELOPMENT_TEAM = 67RAULRX93;
286 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
287 | ENABLE_PREVIEWS = YES;
288 | INFOPLIST_FILE = Info.plist;
289 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
290 | LD_RUNPATH_SEARCH_PATHS = (
291 | "$(inherited)",
292 | "@executable_path/Frameworks",
293 | );
294 | PRODUCT_BUNDLE_IDENTIFIER = com.krzyzanowskim.TextEdit;
295 | PRODUCT_NAME = "$(TARGET_NAME)";
296 | SUPPORTS_MACCATALYST = YES;
297 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
299 | SWIFT_VERSION = 5.0;
300 | TARGETED_DEVICE_FAMILY = "1,2,6";
301 | };
302 | name = Debug;
303 | };
304 | 75EDB9A724840616007483FF /* Release */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
308 | CLANG_ENABLE_MODULES = YES;
309 | CODE_SIGN_ENTITLEMENTS = TextEdit.entitlements;
310 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
311 | CODE_SIGN_STYLE = Automatic;
312 | DEVELOPMENT_TEAM = 67RAULRX93;
313 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
314 | ENABLE_PREVIEWS = YES;
315 | INFOPLIST_FILE = Info.plist;
316 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
317 | LD_RUNPATH_SEARCH_PATHS = (
318 | "$(inherited)",
319 | "@executable_path/Frameworks",
320 | );
321 | PRODUCT_BUNDLE_IDENTIFIER = com.krzyzanowskim.TextEdit;
322 | PRODUCT_NAME = "$(TARGET_NAME)";
323 | SUPPORTS_MACCATALYST = YES;
324 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
325 | SWIFT_VERSION = 5.0;
326 | TARGETED_DEVICE_FAMILY = "1,2,6";
327 | };
328 | name = Release;
329 | };
330 | /* End XCBuildConfiguration section */
331 |
332 | /* Begin XCConfigurationList section */
333 | 75EDB98C24840614007483FF /* Build configuration list for PBXProject "TextEditSample" */ = {
334 | isa = XCConfigurationList;
335 | buildConfigurations = (
336 | 75EDB9A324840616007483FF /* Debug */,
337 | 75EDB9A424840616007483FF /* Release */,
338 | );
339 | defaultConfigurationIsVisible = 0;
340 | defaultConfigurationName = Release;
341 | };
342 | 75EDB9A524840616007483FF /* Build configuration list for PBXNativeTarget "TextEditSample" */ = {
343 | isa = XCConfigurationList;
344 | buildConfigurations = (
345 | 75EDB9A624840616007483FF /* Debug */,
346 | 75EDB9A724840616007483FF /* Release */,
347 | );
348 | defaultConfigurationIsVisible = 0;
349 | defaultConfigurationName = Release;
350 | };
351 | /* End XCConfigurationList section */
352 |
353 | /* Begin XCSwiftPackageProductDependency section */
354 | 7575AD4E264055BD007B28DE /* TextEdit */ = {
355 | isa = XCSwiftPackageProductDependency;
356 | productName = TextEdit;
357 | };
358 | /* End XCSwiftPackageProductDependency section */
359 | };
360 | rootObject = 75EDB98924840614007483FF /* Project object */;
361 | }
362 |
--------------------------------------------------------------------------------
/TextEditSample/TextEditSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TextEditSample/TextEditSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TextEditSample/TextEditingView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import TextEdit
3 |
4 | struct TextEditingView: View {
5 | @State private var text = "type here...\n"
6 | @State private var font = UIFont.preferredFont(forTextStyle: .body) as CTFont
7 | @State private var carretWidth = 2.0 as CGFloat
8 |
9 | var body: some View {
10 | TextEdit(
11 | text: $text,
12 | font: $font,
13 | carretWidth: $carretWidth
14 | )
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------