Slide 1

Slide 1 text

@FromAtom ʰSwiftUIͳΒiOS, macOSͷ྆ํͰಈ͘ΤσΟλΞϓϦ͕؆୯ʹ࡞ΕΔʱ ͱࢥͬͨΒେؒҧ͍ͩΑʂ

Slide 2

Slide 2 text

• ϐΫγϒגࣜձࣾ • iOSΞϓϦ෼໺ςοΫϦʔυ • ޷͖ͳSwift͸ guard @FromAtom

Slide 3

Slide 3 text

ࠓ೔ͷ࿩ͷલఏ

Slide 4

Slide 4 text

࿩ͷલఏ • Memoliaͱ͍͏ݸਓ։ൃΞϓϦ • iOS༻ͷՕ৚ॻ͖ϝϞΞϓϦ • macOS൛΋࡞Γ͍ͨͱࢥ͍ͬͯΔ 動画

Slide 5

Slide 5 text

iOS൛͸΋͏͋Δ͠ SwiftUIͳΒ macOSରԠ΋͙͢΍Ζʂ

Slide 6

Slide 6 text

import SwiftUI struct ContentView: View { @State var text: String = "" var body: some View { TextEditor(text: $text) .padding() } }

Slide 7

Slide 7 text

౰વiPadͰ͸ͪΌΜͱಈ͘ 動画

Slide 8

Slide 8 text

͜ΕΛmacOSͰ΋ಈ͔ͦ͏

Slide 9

Slide 9 text

macOS༻ʹϏϧυ͢Δखஈ

Slide 10

Slide 10 text

macOS༻ʹϏϧυ͢Δखஈ • macOS SDKΛ࢖͏ • UIKitܥͷiOSࢿݯ͸࢖͑ͳ͍ • AppKit͕࢖͑Δ

Slide 11

Slide 11 text

macOS༻ʹϏϧυ͢Δखஈ • Mac Catalystʛ • iPadΞϓϦΛmacOSͰಈ͔ͤΔ • UIKit΍iOSͷࢿݯ͕࢖͑Δ

Slide 12

Slide 12 text

macOS༻ʹϏϧυ͢Δखஈ • Designed for iPadʛ • iPadΞϓϦΛmacOSͰಈ͔ͤΔ ‣ AppleγϦίϯ౥ࡌͷMacͷΈ • UIKit΍iOSͷࢿݯ͕࢖͑Δ

Slide 13

Slide 13 text

ͦΕͧΕϏϧυͯ͠ΈΔ

Slide 14

Slide 14 text

ͦΕͧΕͰϏϧυͯ͠ΈΔ 動画

Slide 15

Slide 15 text

ͦΕͧΕͰϏϧυͯ͠ΈΔ 動画

Slide 16

Slide 16 text

ͦΕͧΕͰϏϧυͯ͠ΈΔ 動画

Slide 17

Slide 17 text

IMEͷม׵ީิͰจࣈ͕ݟ͑Μʂ Catalyst Designed for iPad Google IMEͰ͸ͳ͘͜ͱ͑ΓΛ࢖ͬͯ΋ಉ݁͡ՌͩΑ΍ͬͨͶ

Slide 18

Slide 18 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 19

Slide 19 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 20

Slide 20 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 21

Slide 21 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 22

Slide 22 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 23

Slide 23 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 24

Slide 24 text

ରԠҊ macOS SDK Catalyst Designed for iPad SwiftUI.TextEditor ̋ ✕ ✕ UIKit + Storyboard ϏϧυෆՄ ✕ ✕ AppKit ̋ ϏϧυෆՄ ϏϧυෆՄ UIViewRepresentable (UITextView) ϏϧυෆՄ ✕ ✕ NSViewRepresentable (NSTextView) ̋ ϏϧυෆՄ ϏϧυෆՄ

Slide 25

Slide 25 text

macOS SDKͰϏϧυ͢Δ

Slide 26

Slide 26 text

ݴ͏͸қ͘ߦ͏͸೉͠ • ؾ͕͍ͭͨΒ࢖ͬͯ͠·͏UIKit • UIܥϥΠϒϥϦ͸masOS͸ະରԠ͕ͪ • Conditional Compilation Block·ΈΕʹͳΔ ‣ #if os(macOS) Έ͍ͨͳ΍ͭ

Slide 27

Slide 27 text

ؤுΓ·͠ΐ͏

Slide 28

Slide 28 text

ೖྗิ׬ػೳΛ࡞Γ͍ͨ

Slide 29

Slide 29 text

ࠓ೔࿩͢ೖྗิ׬ػೳ • ͳʹ͔ೖྗͨ͠Βิ׬͞ΕΔ ‣ ྫɿMarkdownͷՕ৚ॻ͖ 動画

Slide 30

Slide 30 text

Enter͕ೖྗ͞ΕͨΒ ॲཧΛ૸ΒͤΕ͹͑͑΍Μ 😁

Slide 31

Slide 31 text

Enter͕ೖྗ͞ΕͨΒ ॲཧΛ૸ΒͤΕ͹͑͑΍Μ 😁

Slide 32

Slide 32 text

SwiftUI.TextEditorͰ࢖͑ͦ͏ͳAPI • onChange • onSubmit • onKeyPress

Slide 33

Slide 33 text

onChangeͷ৔߹ struct ContentView: View { @State var text: String = "" var body: some View { TextEditor(text: $text) // iOS 14.0–17.0 Deprecated .onChange(of: text, perform: { newValue in // do something }) // iOS 17.0+ .onChange(of: text) { oldValue, newValue in // do something } } } • text͕มߋ͞ΕΔͨͼʹൃՐ • ࠩ෼͸ੜ੒Ͱ͖Δ ‣ ͲͷߦͰEnter͞Ε͔ͨܭࢉෆՄ ‣ SelectedRange͕औಘෆՄ • ࢖͑ͳ͍

Slide 34

Slide 34 text

onSubmitͷ৔߹ struct ContentView: View { @State var text: String = "" @FocusState private var isFocused: Bool var body: some View { TextEditor(text: $text) .onSubmit { } } } • TextEditorͰ͸ൃՐ͠ͳ͍ ‣ ͳΜͰʁ • lineLimitΛࢦఆͯ͠΋μϝ ‣ ͔ͯࢦఆͯ͠΋ޮ͔ͳ͍ • submitLabelΛࢦఆͯ͠΋μϝ • ࢖͑ͳ͍

Slide 35

Slide 35 text

onKeyPressͷ৔߹ struct ContentView: View { @State var text: String = "" var body: some View { TextEditor(text: $text) .onKeyPress(.return, action: { print("Enter") return .ignored }) } } • ϋʔυ΢ΣΞΩʔϘʔυͷΈ • ม׵֬ఆͷEnterͰ΋ൃՐ ‣ markedTextRange͸औಘෆՄ • ࢖͑ͳ͍

Slide 36

Slide 36 text

࢖͑ΔAPI͕ͳ͍ 😢

Slide 37

Slide 37 text

swiftui-introspect

Slide 38

Slide 38 text

swiftui-introspectͷͬ͘͟Γઆ໌ • SwiftUIͷཪଆʹ͍ΔUIKit / AppKitཁૉʹΞΫηεͰ͖Δ • TextEditorͷཪʹ͍ΔUITextView / NSTextView͕औಘͰ͖Δ TextEditor(text: $text) .introspect(.textEditor, on: .iOS(.v16, .v17)) { print(type(of: $0)) // => UITextView } .introspect(.textEditor, on: .macOS(.v13, .v14)) { print(type(of: $0)) // => NSTextView }

Slide 39

Slide 39 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect struct ContentView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() final class Delegate: NSObject, NSTextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } var body: some View { TextEditor(text: $text) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } } }

Slide 40

Slide 40 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect struct ContentView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() final class Delegate: NSObject, NSTextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } var body: some View { TextEditor(text: $text) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } } }

Slide 41

Slide 41 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect struct ContentView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() final class Delegate: NSObject, NSTextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } var body: some View { TextEditor(text: $text) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } } }

Slide 42

Slide 42 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect struct ContentView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() final class Delegate: NSObject, NSTextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } var body: some View { TextEditor(text: $text) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } } }

Slide 43

Slide 43 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect struct ContentView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() final class Delegate: NSObject, NSTextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } var body: some View { TextEditor(text: $text) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } } } ͋ͱ͸ίπίπ࣮૷͢Ε͹OK

Slide 44

Slide 44 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif struct EditorView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif var body: some View { TextEditor(text: $text) #if os(macOS) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } #elseif os(iOS) .introspect(.textEditor, on: .iOS(.v16, .v17)) { textView in self.textView = textView } #endif } }

Slide 45

Slide 45 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif struct EditorView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif var body: some View { TextEditor(text: $text) #if os(macOS) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } #elseif os(iOS) .introspect(.textEditor, on: .iOS(.v16, .v17)) { textView in self.textView = textView } #endif } } import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif

Slide 46

Slide 46 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif struct EditorView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif var body: some View { TextEditor(text: $text) #if os(macOS) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } #elseif os(iOS) .introspect(.textEditor, on: .iOS(.v16, .v17)) { textView in self.textView = textView } #endif } } #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif

Slide 47

Slide 47 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif struct EditorView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif var body: some View { TextEditor(text: $text) #if os(macOS) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } #elseif os(iOS) .introspect(.textEditor, on: .iOS(.v16, .v17)) { textView in self.textView = textView } #endif } } var body: some View { TextEditor(text: $text) #if os(macOS) .introspect( .textEditor, on: .macOS(.v13, .v14) ) { textView in self.textView = textView } #elseif os(iOS) .introspect( .textEditor, on: .iOS(.v16, .v17) ) { textView in self.textView = textView } #endif }

Slide 48

Slide 48 text

import SwiftUI @_spi(Advanced) import SwiftUIIntrospect #if os(macOS) import AppKit public typealias TextView = NSTextView public typealias TextViewDelegate = NSTextViewDelegate #elseif os(iOS) import UIKit public typealias TextView = UITextView public typealias TextViewDelegate = UITextViewDelegate #endif struct EditorView: View { @State var text: String = "" @Weak var textView: TextView? { didSet { textView?.delegate = delegate } } var delegate = Delegate() #if os(macOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { guard !textView.hasMarkedText() else { return true } if replacementString == "\n" { print("Enterが入力されたよ") } return true } } #elseif os(iOS) final class Delegate: NSObject, TextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard textView.markedTextRange == nil else { return true } if text == "\n" { print("Enterが入力されたよ") } return true } } #endif var body: some View { TextEditor(text: $text) #if os(macOS) .introspect(.textEditor, on: .macOS(.v13, .v14)) { textView in self.textView = textView } #elseif os(iOS) .introspect(.textEditor, on: .iOS(.v16, .v17)) { textView in self.textView = textView } #endif } } ͚ͬ͜͏େม͚ͩͲ iOS, macOS྆ରԠͯ͠ SwiftUIͰ࣮૷Ͱ͖Δ

Slide 49

Slide 49 text

ͪɹͳɹΈɹʹ

Slide 50

Slide 50 text

textView.delegateΛ্ॻ͖͢Δͱ ςΩετมߋΠϕϯτ͕ൃՐ͠ͳ͍ 🫠

Slide 51

Slide 51 text

@Weak var textView: TextView? { didSet { textView?.delegate = delegate } } -略- var body: some View { TextEditor(text: $text) .introspect( .textEditor, on: .iOS(.v16, .v17) ) { textView in self.textView = textView } .onChange(of: text, { print(text) // 呼ばれない }) } @Weak var textView: TextView? { didSet { //textView?.delegate = delegate } } -略- var body: some View { TextEditor(text: $text) .introspect( .textEditor, on: .iOS(.v16, .v17) ) { textView in self.textView = textView } .onChange(of: text, { print(text) // 呼ばれる }) }

Slide 52

Slide 52 text

TextEditor( text: viewStore.binding( get: { $0.text }, send: { .someAction // 呼ばれない }() ) ) TCAͰbinding͍ͯ͠Δ৔߹

Slide 53

Slide 53 text

SwiftUI͕ར༻͍ͯ͠ΔDelegateΛ ্ॻ͖ͯ͠Δ͔Βಈ͔ͳ͘ͳΔͷ͔΋ʁ 🫠

Slide 54

Slide 54 text

·ͱΊ • ม׵࣌ʹจࣈ͕ӅΕͯ͠·͏ͷͰmacOS SDKͰϏϧυ͠Α͏ • swiftui-introspectΛ࢖ͬͯUITextView,NSTextViewΛऔಘ͠Α͏ ‣ UIViewRepresentable, NSViewRepresentableͱ͍͏ख΋͋ΔΑ • swiftui-introspectͰdelegateΛ্ॻ͖͢ΔͱมߋΠϕϯτ͕ඈΜͰ͜ͳ͍ͷͰ஫ҙ • Ͳͷํ਑Ͱ΋େมͳͷͰؤுΖ͏ ‣ ϩδοΫΛ෼཭Ͱ͖Δʢ͠΍͍͢ʣΞʔΩςΫνϟ΍ઃܭΛ͠·͠ΐ͏

Slide 55

Slide 55 text

ࢀߟɿݸਓ։ൃΞϓϦMemoliaͰ͸Ͳ͏͢Δ͔ʁ • SwiftUI + swiftui-introspectΛ࢖͍ͬͯΔ ‣ কདྷతʹSwiftUI.TextEditor͕֦ு͞ΕΔͱ৴ͯ͡ • onChange͕ൃՐ͠ͳ͍ͷ͸ٯʹOKͱߟ͍͑ͯΔ ‣ ૒ํ޲όΠϯσΟϯά͕ࢮ͵ͷͰTCAͰ୯ҰσʔλϑϩʔʹͰ͖Δ • swiftui-introspectܦ༝Ͱ͸ΠϕϯτൃՐ͚ͩͤͯ͞ϩδοΫ͸શ෦෼཭ ‣ TextEditor͕֦ு͞ΕͨΒΠϕϯτൃՐݩΛม͑Ε͹͍͍͚ͩʢͷ͸ͣʣ