mirror of
https://github.com/writeas/writefreely-swiftui-multiplatform.git
synced 2024-11-15 01:11:02 +00:00
Improve editor scrolling on ios (#229)
* Unset isScrollEnabled property on UITextViews * Begin implementing common MultilineTextView * Remove legacy text views * Fix firstResponder issues * Bump version and build number and update change log * Fix smart-dashes replacement in MultilineTextView * Wait 10ms before navigating to the editor after creating a new post * Wait before navigating to editor after creating a new post * Bump build number
This commit is contained in:
parent
ceb7eaa2ac
commit
f256996161
@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- [Mac] Fixed a bug where alerts weren't presented for login errors.
|
- [Mac] Fixed a bug where alerts weren't presented for login errors.
|
||||||
- [Mac] Fixed some build warnings in the project.
|
- [Mac] Fixed some build warnings in the project.
|
||||||
- [Mac] Bumped WriteFreely package to v0.3.6 to handle decoding of fractional seconds in dates.
|
- [Mac] Bumped WriteFreely package to v0.3.6 to handle decoding of fractional seconds in dates.
|
||||||
|
- [iOS] Fixed an issue that made it tricky to scroll in the post editor.
|
||||||
|
- [iOS] Fixed a bug that didn't navigate to the post editor after tapping the new-post button.
|
||||||
|
|
||||||
## [1.0.12-ios] - 2022-10-06
|
## [1.0.12-ios] - 2022-10-06
|
||||||
|
|
||||||
|
@ -47,7 +47,9 @@ struct PostListView: View {
|
|||||||
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
|
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.model.showAllPosts = false
|
self.model.showAllPosts = false
|
||||||
self.model.selectedPost = managedPost
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
self.model.selectedPost = managedPost
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, label: {
|
}, label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -14,7 +14,7 @@ struct PostStatusBadgeView: View {
|
|||||||
.padding(EdgeInsets(top: 2.5, leading: 7.5, bottom: 2.5, trailing: 7.5))
|
.padding(EdgeInsets(top: 2.5, leading: 7.5, bottom: 2.5, trailing: 7.5))
|
||||||
.background(badgeColor)
|
.background(badgeColor)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5.0, style: .circular))
|
.clipShape(RoundedRectangle(cornerRadius: 5.0, style: .circular))
|
||||||
.frame(width: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupBadgeProperties(for status: PostStatus) -> (String, Color) {
|
func setupBadgeProperties(for status: PostStatus) -> (String, Color) {
|
||||||
|
@ -90,8 +90,6 @@
|
|||||||
17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; };
|
17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; };
|
||||||
17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; };
|
17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; };
|
||||||
17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A67CAE251A5DD7002F163D /* PostEditorView.swift */; };
|
17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A67CAE251A5DD7002F163D /* PostEditorView.swift */; };
|
||||||
17AD0A5E25489E810057D763 /* PostTitleTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17AD0A5D25489E810057D763 /* PostTitleTextView.swift */; };
|
|
||||||
17AD0A6425489E900057D763 /* PostBodyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17AD0A6325489E900057D763 /* PostBodyTextView.swift */; };
|
|
||||||
17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; };
|
17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; };
|
||||||
17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; };
|
17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; };
|
||||||
17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; };
|
17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; };
|
||||||
@ -138,6 +136,7 @@
|
|||||||
17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; };
|
17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; };
|
||||||
17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; };
|
17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; };
|
||||||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; };
|
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; };
|
||||||
|
375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -227,8 +226,6 @@
|
|||||||
17A5388D24DDEC7400DEFF9A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
|
17A5388D24DDEC7400DEFF9A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
|
||||||
17A5389124DDED0000DEFF9A /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
17A5389124DDED0000DEFF9A /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||||
17A67CAE251A5DD7002F163D /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = "<group>"; };
|
17A67CAE251A5DD7002F163D /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = "<group>"; };
|
||||||
17AD0A5D25489E810057D763 /* PostTitleTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTitleTextView.swift; sourceTree = "<group>"; };
|
|
||||||
17AD0A6325489E900057D763 /* PostBodyTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBodyTextView.swift; sourceTree = "<group>"; };
|
|
||||||
17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+Keychain.swift"; sourceTree = "<group>"; };
|
17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+Keychain.swift"; sourceTree = "<group>"; };
|
||||||
17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+API.swift"; sourceTree = "<group>"; };
|
17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+API.swift"; sourceTree = "<group>"; };
|
||||||
17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+APIHandlers.swift"; sourceTree = "<group>"; };
|
17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+APIHandlers.swift"; sourceTree = "<group>"; };
|
||||||
@ -270,6 +267,7 @@
|
|||||||
17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = "<group>"; };
|
17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = "<group>"; };
|
||||||
17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; };
|
17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; };
|
||||||
17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; };
|
17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; };
|
||||||
|
375A67E728FC555C007A1AC0 /* MultilineTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -436,8 +434,7 @@
|
|||||||
1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */,
|
1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */,
|
||||||
173E19D0254318F600440F0F /* RemoteChangePromptView.swift */,
|
173E19D0254318F600440F0F /* RemoteChangePromptView.swift */,
|
||||||
173E19E2254329CC00440F0F /* PostTextEditingView.swift */,
|
173E19E2254329CC00440F0F /* PostTextEditingView.swift */,
|
||||||
17AD0A5D25489E810057D763 /* PostTitleTextView.swift */,
|
375A67E728FC555C007A1AC0 /* MultilineTextView.swift */,
|
||||||
17AD0A6325489E900057D763 /* PostBodyTextView.swift */,
|
|
||||||
);
|
);
|
||||||
path = PostEditor;
|
path = PostEditor;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -926,13 +923,12 @@
|
|||||||
17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */,
|
17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */,
|
||||||
17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */,
|
17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */,
|
||||||
17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */,
|
17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */,
|
||||||
17AD0A6425489E900057D763 /* PostBodyTextView.swift in Sources */,
|
|
||||||
17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */,
|
17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */,
|
||||||
17AD0A5E25489E810057D763 /* PostTitleTextView.swift in Sources */,
|
|
||||||
17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */,
|
17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */,
|
||||||
171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */,
|
171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */,
|
||||||
1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */,
|
1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */,
|
||||||
17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */,
|
17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */,
|
||||||
|
375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */,
|
||||||
171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */,
|
171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */,
|
||||||
1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */,
|
1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */,
|
||||||
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */,
|
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */,
|
||||||
@ -1054,7 +1050,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 680;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1066,7 +1062,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.12;
|
MARKETING_VERSION = 1.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -1085,7 +1081,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 680;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1097,7 +1093,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.12;
|
MARKETING_VERSION = 1.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -1228,7 +1224,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 680;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
@ -1237,7 +1233,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.12;
|
MARKETING_VERSION = 1.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
|
||||||
PRODUCT_NAME = "WriteFreely-MultiPlatform";
|
PRODUCT_NAME = "WriteFreely-MultiPlatform";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -1254,7 +1250,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 680;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
@ -1263,7 +1259,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.12;
|
MARKETING_VERSION = 1.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
|
||||||
PRODUCT_NAME = "WriteFreely-MultiPlatform";
|
PRODUCT_NAME = "WriteFreely-MultiPlatform";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -1282,7 +1278,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 676;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@ -1309,7 +1305,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 676;
|
CURRENT_PROJECT_VERSION = 687;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@ -1489,7 +1485,7 @@
|
|||||||
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMinorVersion;
|
kind = upToNextMinorVersion;
|
||||||
minimumVersion = 2.0.0;
|
minimumVersion = 2.3.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
159
iOS/PostEditor/MultilineTextView.swift
Normal file
159
iOS/PostEditor/MultilineTextView.swift
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Credit: https://stackoverflow.com/a/58639072
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
private struct UITextViewWrapper: UIViewRepresentable {
|
||||||
|
typealias UIViewType = UITextView
|
||||||
|
|
||||||
|
@Binding var text: String
|
||||||
|
@Binding var calculatedHeight: CGFloat
|
||||||
|
@Binding var isEditing: Bool
|
||||||
|
var textStyle: UIFont
|
||||||
|
var onDone: (() -> Void)?
|
||||||
|
|
||||||
|
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
|
||||||
|
let textField = UITextView()
|
||||||
|
textField.delegate = context.coordinator
|
||||||
|
|
||||||
|
textField.isEditable = true
|
||||||
|
textField.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
textField.isSelectable = true
|
||||||
|
textField.isUserInteractionEnabled = true
|
||||||
|
textField.isScrollEnabled = false
|
||||||
|
textField.backgroundColor = UIColor.clear
|
||||||
|
textField.smartDashesType = .no
|
||||||
|
|
||||||
|
let font = textStyle
|
||||||
|
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
|
||||||
|
textField.font = fontMetrics.scaledFont(for: font)
|
||||||
|
|
||||||
|
if nil != onDone {
|
||||||
|
textField.returnKeyType = .next
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
return textField
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
|
||||||
|
if uiView.text != self.text {
|
||||||
|
uiView.text = self.text
|
||||||
|
}
|
||||||
|
|
||||||
|
if uiView.window != nil, isEditing {
|
||||||
|
uiView.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {
|
||||||
|
let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
if result.wrappedValue != newSize.height {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
result.wrappedValue = newSize.height // !! must be called asynchronously
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
return Coordinator(text: $text, height: $calculatedHeight, isFirstResponder: $isEditing, onDone: onDone)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Coordinator: NSObject, UITextViewDelegate {
|
||||||
|
@Binding var isFirstResponder: Bool
|
||||||
|
var text: Binding<String>
|
||||||
|
var calculatedHeight: Binding<CGFloat>
|
||||||
|
var onDone: (() -> Void)?
|
||||||
|
|
||||||
|
init(
|
||||||
|
text: Binding<String>,
|
||||||
|
height: Binding<CGFloat>,
|
||||||
|
isFirstResponder: Binding<Bool>,
|
||||||
|
onDone: (() -> Void)? = nil
|
||||||
|
) {
|
||||||
|
self.text = text
|
||||||
|
self.calculatedHeight = height
|
||||||
|
self._isFirstResponder = isFirstResponder
|
||||||
|
self.onDone = onDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChange(_ uiView: UITextView) {
|
||||||
|
text.wrappedValue = uiView.text
|
||||||
|
UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
|
if let onDone = self.onDone, text == "\n" {
|
||||||
|
textView.resignFirstResponder()
|
||||||
|
onDone()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidEndEditing(_ textView: UITextView) {
|
||||||
|
self.isFirstResponder = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MultilineTextField: View {
|
||||||
|
|
||||||
|
private var placeholder: String
|
||||||
|
private var textStyle: UIFont
|
||||||
|
private var onCommit: (() -> Void)?
|
||||||
|
|
||||||
|
@Binding var isFirstResponder: Bool
|
||||||
|
@Binding private var text: String
|
||||||
|
private var internalText: Binding<String> {
|
||||||
|
Binding<String>(get: { self.text }) { // swiftlint:disable:this multiple_closures_with_trailing_closure
|
||||||
|
self.text = $0
|
||||||
|
self.showingPlaceholder = $0.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var dynamicHeight: CGFloat = 100
|
||||||
|
@State private var showingPlaceholder = false
|
||||||
|
|
||||||
|
init (
|
||||||
|
_ placeholder: String = "",
|
||||||
|
text: Binding<String>,
|
||||||
|
font: UIFont,
|
||||||
|
isFirstResponder: Binding<Bool>,
|
||||||
|
onCommit: (() -> Void)? = nil
|
||||||
|
) {
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.onCommit = onCommit
|
||||||
|
self.textStyle = font
|
||||||
|
self._isFirstResponder = isFirstResponder
|
||||||
|
self._text = text
|
||||||
|
self._showingPlaceholder = State<Bool>(initialValue: self.text.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
UITextViewWrapper(
|
||||||
|
text: self.internalText,
|
||||||
|
calculatedHeight: $dynamicHeight,
|
||||||
|
isEditing: $isFirstResponder,
|
||||||
|
textStyle: textStyle,
|
||||||
|
onDone: onCommit
|
||||||
|
)
|
||||||
|
.frame(minHeight: dynamicHeight, maxHeight: dynamicHeight)
|
||||||
|
.background(placeholderView, alignment: .topLeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
var placeholderView: some View {
|
||||||
|
Group {
|
||||||
|
if showingPlaceholder {
|
||||||
|
let font = Font(textStyle)
|
||||||
|
Text(placeholder).foregroundColor(.gray)
|
||||||
|
.padding(.leading, 4)
|
||||||
|
.padding(.top, 8)
|
||||||
|
.font(font)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,134 +0,0 @@
|
|||||||
// Based on https://stackoverflow.com/a/56508132 and https://stackoverflow.com/a/48360549
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class PostBodyCoordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
|
|
||||||
@Binding var text: String
|
|
||||||
@Binding var isFirstResponder: Bool
|
|
||||||
var lineSpacingMultiplier: CGFloat
|
|
||||||
var didBecomeFirstResponder: Bool = false
|
|
||||||
var postBodyTextView: PostBodyTextView
|
|
||||||
|
|
||||||
weak var textView: UITextView?
|
|
||||||
|
|
||||||
init(
|
|
||||||
_ textView: PostBodyTextView,
|
|
||||||
text: Binding<String>,
|
|
||||||
isFirstResponder: Binding<Bool>,
|
|
||||||
lineSpacingMultiplier: CGFloat
|
|
||||||
) {
|
|
||||||
self.postBodyTextView = textView
|
|
||||||
_text = text
|
|
||||||
_isFirstResponder = isFirstResponder
|
|
||||||
self.lineSpacingMultiplier = lineSpacingMultiplier
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
updateSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSize() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard let view = self.textView else { return }
|
|
||||||
let size = view.sizeThatFits(view.bounds.size)
|
|
||||||
if self.postBodyTextView.height != size.height {
|
|
||||||
self.postBodyTextView.height = size.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.postBodyTextView.text = textView.text ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func textViewDidEndEditing(_ textView: UITextView) {
|
|
||||||
self.isFirstResponder = false
|
|
||||||
self.didBecomeFirstResponder = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutManager(
|
|
||||||
_ layoutManager: NSLayoutManager,
|
|
||||||
didCompleteLayoutFor textContainer: NSTextContainer?,
|
|
||||||
atEnd layoutFinishedFlag: Bool
|
|
||||||
) {
|
|
||||||
updateSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutManager(
|
|
||||||
_ layoutManager: NSLayoutManager,
|
|
||||||
lineSpacingAfterGlyphAt glyphIndex: Int,
|
|
||||||
withProposedLineFragmentRect rect: CGRect
|
|
||||||
) -> CGFloat {
|
|
||||||
// HACK: - This seems to be the only way to get line spacing to update dynamically on iPad
|
|
||||||
// when switching between full-screen, split-screen, and slide-over views.
|
|
||||||
if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first {
|
|
||||||
// Get the width of the window to determine the size class
|
|
||||||
if window.frame.width < 600 {
|
|
||||||
// Use 0.25 multiplier for compact size class
|
|
||||||
return 17 * 0.25
|
|
||||||
} else {
|
|
||||||
// Use 0.5 multiplier otherwise
|
|
||||||
return 17 * 0.5
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 17 * lineSpacingMultiplier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PostBodyTextView: UIViewRepresentable {
|
|
||||||
@Binding var text: String
|
|
||||||
@Binding var textStyle: UIFont
|
|
||||||
@Binding var height: CGFloat
|
|
||||||
@Binding var isFirstResponder: Bool
|
|
||||||
@State var lineSpacing: CGFloat
|
|
||||||
|
|
||||||
func makeUIView(context: UIViewRepresentableContext<PostBodyTextView>) -> UITextView {
|
|
||||||
let textView = UITextView()
|
|
||||||
|
|
||||||
textView.isEditable = true
|
|
||||||
textView.isUserInteractionEnabled = true
|
|
||||||
textView.isScrollEnabled = true
|
|
||||||
textView.alwaysBounceVertical = false
|
|
||||||
textView.smartDashesType = .no
|
|
||||||
|
|
||||||
context.coordinator.textView = textView
|
|
||||||
textView.delegate = context.coordinator
|
|
||||||
textView.layoutManager.delegate = context.coordinator
|
|
||||||
|
|
||||||
let font = textStyle
|
|
||||||
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
|
|
||||||
textView.font = fontMetrics.scaledFont(for: font)
|
|
||||||
|
|
||||||
textView.backgroundColor = UIColor.clear
|
|
||||||
|
|
||||||
return textView
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCoordinator() -> PostBodyCoordinator {
|
|
||||||
return Coordinator(
|
|
||||||
self,
|
|
||||||
text: $text,
|
|
||||||
isFirstResponder: $isFirstResponder,
|
|
||||||
lineSpacingMultiplier: lineSpacing
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<PostBodyTextView>) {
|
|
||||||
if uiView.text != text {
|
|
||||||
uiView.text = text
|
|
||||||
}
|
|
||||||
|
|
||||||
let font = textStyle
|
|
||||||
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
|
|
||||||
uiView.font = fontMetrics.scaledFont(for: font)
|
|
||||||
|
|
||||||
// We don't want the text field to become first responder every time SwiftUI refreshes the view.
|
|
||||||
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
|
|
||||||
uiView.becomeFirstResponder()
|
|
||||||
context.coordinator.didBecomeFirstResponder = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,8 +7,6 @@ struct PostTextEditingView: View {
|
|||||||
@Binding var updatingBodyFromServer: Bool
|
@Binding var updatingBodyFromServer: Bool
|
||||||
@State private var appearance: PostAppearance = .serif
|
@State private var appearance: PostAppearance = .serif
|
||||||
@State private var titleTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 26)!
|
@State private var titleTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 26)!
|
||||||
@State private var titleTextHeight: CGFloat = 50
|
|
||||||
@State private var bodyTextHeight: CGFloat = 50
|
|
||||||
@State private var titleIsFirstResponder: Bool = true
|
@State private var titleIsFirstResponder: Bool = true
|
||||||
@State private var bodyTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 17)!
|
@State private var bodyTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 17)!
|
||||||
@State private var bodyIsFirstResponder: Bool = false
|
@State private var bodyIsFirstResponder: Bool = false
|
||||||
@ -26,77 +24,39 @@ struct PostTextEditingView: View {
|
|||||||
UITextView.appearance().backgroundColor = .clear
|
UITextView.appearance().backgroundColor = .clear
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleFieldHeight: CGFloat {
|
|
||||||
let minHeight: CGFloat = textEditorHeight
|
|
||||||
if titleTextHeight < minHeight {
|
|
||||||
return minHeight
|
|
||||||
}
|
|
||||||
return titleTextHeight
|
|
||||||
}
|
|
||||||
var bodyFieldHeight: CGFloat {
|
|
||||||
let minHeight: CGFloat = textEditorHeight
|
|
||||||
if bodyTextHeight < minHeight {
|
|
||||||
return minHeight
|
|
||||||
}
|
|
||||||
return bodyTextHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
ZStack(alignment: .topLeading) {
|
MultilineTextField(
|
||||||
if post.title.count == 0 {
|
"Title (optional)",
|
||||||
Text("Title (optional)")
|
text: $post.title,
|
||||||
.font(Font(titleTextStyle))
|
font: titleTextStyle,
|
||||||
.foregroundColor(Color(UIColor.placeholderText))
|
isFirstResponder: $titleIsFirstResponder,
|
||||||
.padding(.horizontal, 4)
|
onCommit: didFinishEditingTitle
|
||||||
.padding(.vertical, 8)
|
)
|
||||||
.accessibilityHidden(true)
|
.accessibilityLabel(Text("Title (optional)"))
|
||||||
|
.accessibilityHint(Text("Add or edit the title for your post; use the Return key to skip to the body"))
|
||||||
|
.onChange(of: post.title) { _ in
|
||||||
|
if post.status == PostStatus.published.rawValue && !updatingTitleFromServer {
|
||||||
|
post.status = PostStatus.edited.rawValue
|
||||||
}
|
}
|
||||||
PostTitleTextView(
|
if updatingTitleFromServer {
|
||||||
text: $post.title,
|
updatingTitleFromServer = false
|
||||||
textStyle: $titleTextStyle,
|
|
||||||
height: $titleTextHeight,
|
|
||||||
isFirstResponder: $titleIsFirstResponder,
|
|
||||||
lineSpacing: horizontalSizeClass == .compact ? lineSpacingMultiplier / 2 : lineSpacingMultiplier
|
|
||||||
)
|
|
||||||
.accessibilityLabel(Text("Title (optional)"))
|
|
||||||
.accessibilityHint(Text("Add or edit the title for your post; use the Return key to skip to the body"))
|
|
||||||
.frame(height: titleFieldHeight)
|
|
||||||
.onChange(of: post.title) { _ in
|
|
||||||
if post.status == PostStatus.published.rawValue && !updatingTitleFromServer {
|
|
||||||
post.status = PostStatus.edited.rawValue
|
|
||||||
}
|
|
||||||
if updatingTitleFromServer {
|
|
||||||
updatingTitleFromServer = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ZStack(alignment: .topLeading) {
|
MultilineTextField(
|
||||||
if post.body.count == 0 {
|
"Write...",
|
||||||
Text("Write…")
|
text: $post.body,
|
||||||
.font(Font(bodyTextStyle))
|
font: bodyTextStyle,
|
||||||
.foregroundColor(Color(UIColor.placeholderText))
|
isFirstResponder: $bodyIsFirstResponder
|
||||||
.padding(.horizontal, 4)
|
)
|
||||||
.padding(.vertical, 8)
|
.accessibilityLabel(Text("Body"))
|
||||||
.accessibilityHidden(true)
|
.accessibilityHint(Text("Add or edit the body of your post"))
|
||||||
|
.onChange(of: post.body) { _ in
|
||||||
|
if post.status == PostStatus.published.rawValue && !updatingBodyFromServer {
|
||||||
|
post.status = PostStatus.edited.rawValue
|
||||||
}
|
}
|
||||||
PostBodyTextView(
|
if updatingBodyFromServer {
|
||||||
text: $post.body,
|
updatingBodyFromServer = false
|
||||||
textStyle: $bodyTextStyle,
|
|
||||||
height: $bodyTextHeight,
|
|
||||||
isFirstResponder: $bodyIsFirstResponder,
|
|
||||||
lineSpacing: horizontalSizeClass == .compact ? lineSpacingMultiplier / 2 : lineSpacingMultiplier
|
|
||||||
)
|
|
||||||
.frame(height: bodyFieldHeight)
|
|
||||||
.accessibilityLabel(Text("Body"))
|
|
||||||
.accessibilityHint(Text("Add or edit the body of your post"))
|
|
||||||
.onChange(of: post.body) { _ in
|
|
||||||
if post.status == PostStatus.published.rawValue && !updatingBodyFromServer {
|
|
||||||
post.status = PostStatus.edited.rawValue
|
|
||||||
}
|
|
||||||
if updatingBodyFromServer {
|
|
||||||
updatingBodyFromServer = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,4 +76,9 @@ struct PostTextEditingView: View {
|
|||||||
self.bodyTextStyle = UIFont(name: appearance.rawValue, size: 17)!
|
self.bodyTextStyle = UIFont(name: appearance.rawValue, size: 17)!
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func didFinishEditingTitle() {
|
||||||
|
self.titleIsFirstResponder = false
|
||||||
|
self.bodyIsFirstResponder = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
// Based on https://lostmoa.com/blog/DynamicHeightForTextFieldInSwiftUI and https://stackoverflow.com/a/56508132
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class PostTitleCoordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
|
|
||||||
@Binding var text: String
|
|
||||||
@Binding var isFirstResponder: Bool
|
|
||||||
var lineSpacingMultiplier: CGFloat
|
|
||||||
var didBecomeFirstResponder: Bool = false
|
|
||||||
var postTitleTextView: PostTitleTextView
|
|
||||||
|
|
||||||
weak var textView: UITextView?
|
|
||||||
|
|
||||||
init(
|
|
||||||
_ textView: PostTitleTextView,
|
|
||||||
text: Binding<String>,
|
|
||||||
isFirstResponder: Binding<Bool>,
|
|
||||||
lineSpacingMultiplier: CGFloat
|
|
||||||
) {
|
|
||||||
self.postTitleTextView = textView
|
|
||||||
_text = text
|
|
||||||
_isFirstResponder = isFirstResponder
|
|
||||||
self.lineSpacingMultiplier = lineSpacingMultiplier
|
|
||||||
}
|
|
||||||
|
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.postTitleTextView.text = textView.text ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
||||||
if text == "\n" {
|
|
||||||
self.isFirstResponder.toggle()
|
|
||||||
self.didBecomeFirstResponder = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutManager(
|
|
||||||
_ layoutManager: NSLayoutManager,
|
|
||||||
didCompleteLayoutFor textContainer: NSTextContainer?,
|
|
||||||
atEnd layoutFinishedFlag: Bool
|
|
||||||
) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard let view = self.textView else { return }
|
|
||||||
let size = view.sizeThatFits(view.bounds.size)
|
|
||||||
if self.postTitleTextView.height != size.height {
|
|
||||||
self.postTitleTextView.height = size.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutManager(
|
|
||||||
_ layoutManager: NSLayoutManager,
|
|
||||||
lineSpacingAfterGlyphAt glyphIndex: Int,
|
|
||||||
withProposedLineFragmentRect rect: CGRect
|
|
||||||
) -> CGFloat {
|
|
||||||
// HACK: - This seems to be the only way to get line spacing to update dynamically on iPad
|
|
||||||
// when switching between full-screen, split-screen, and slide-over views.
|
|
||||||
if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first {
|
|
||||||
// Get the width of the window to determine the size class
|
|
||||||
if window.frame.width < 600 {
|
|
||||||
// Use 0.25 multiplier for compact size class
|
|
||||||
return 17 * 0.25
|
|
||||||
} else {
|
|
||||||
// Use 0.5 multiplier otherwise
|
|
||||||
return 17 * 0.5
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 17 * lineSpacingMultiplier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PostTitleTextView: UIViewRepresentable {
|
|
||||||
@Binding var text: String
|
|
||||||
@Binding var textStyle: UIFont
|
|
||||||
@Binding var height: CGFloat
|
|
||||||
@Binding var isFirstResponder: Bool
|
|
||||||
@State var lineSpacing: CGFloat
|
|
||||||
|
|
||||||
func makeUIView(context: UIViewRepresentableContext<PostTitleTextView>) -> UITextView {
|
|
||||||
let textView = UITextView()
|
|
||||||
|
|
||||||
textView.isEditable = true
|
|
||||||
textView.isUserInteractionEnabled = true
|
|
||||||
textView.isScrollEnabled = true
|
|
||||||
textView.alwaysBounceVertical = false
|
|
||||||
|
|
||||||
context.coordinator.textView = textView
|
|
||||||
textView.delegate = context.coordinator
|
|
||||||
textView.layoutManager.delegate = context.coordinator
|
|
||||||
|
|
||||||
let font = textStyle
|
|
||||||
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
|
|
||||||
textView.font = fontMetrics.scaledFont(for: font)
|
|
||||||
|
|
||||||
textView.backgroundColor = UIColor.clear
|
|
||||||
|
|
||||||
return textView
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCoordinator() -> PostTitleCoordinator {
|
|
||||||
return Coordinator(
|
|
||||||
self,
|
|
||||||
text: $text,
|
|
||||||
isFirstResponder: $isFirstResponder,
|
|
||||||
lineSpacingMultiplier: lineSpacing
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<PostTitleTextView>) {
|
|
||||||
if uiView.text != text {
|
|
||||||
uiView.text = text
|
|
||||||
}
|
|
||||||
|
|
||||||
let font = textStyle
|
|
||||||
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
|
|
||||||
uiView.font = fontMetrics.scaledFont(for: font)
|
|
||||||
|
|
||||||
// We don't want the text field to become first responder every time SwiftUI refreshes the view.
|
|
||||||
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
|
|
||||||
uiView.becomeFirstResponder()
|
|
||||||
context.coordinator.didBecomeFirstResponder = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user