diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index b6759c2..e650d83 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -3,25 +3,28 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ + BE65305F2B05D59900922369 /* Focuser in Frameworks */ = {isa = PBXBuildFile; productRef = BE65305E2B05D59900922369 /* Focuser */; }; + BE6530612B05DD0800922369 /* TextEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6530602B05DD0800922369 /* TextEditorView.swift */; }; + BE6530632B05E2E800922369 /* TextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6530622B05E2E800922369 /* TextFieldView.swift */; }; + BE6530652B05E2FB00922369 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6530642B05E2FB00922369 /* RootView.swift */; }; FFDC73A326DF94DC00C1F0D0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDC73A226DF94DC00C1F0D0 /* AppDelegate.swift */; }; FFDC73A526DF94DC00C1F0D0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDC73A426DF94DC00C1F0D0 /* SceneDelegate.swift */; }; - FFDC73A726DF94DC00C1F0D0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDC73A626DF94DC00C1F0D0 /* ContentView.swift */; }; FFDC73A926DF94DE00C1F0D0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FFDC73A826DF94DE00C1F0D0 /* Assets.xcassets */; }; FFDC73AC26DF94DE00C1F0D0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FFDC73AB26DF94DE00C1F0D0 /* Preview Assets.xcassets */; }; FFDC73AF26DF94DE00C1F0D0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FFDC73AD26DF94DE00C1F0D0 /* LaunchScreen.storyboard */; }; - FFDC73C626DFE61F00C1F0D0 /* Focuser in Frameworks */ = {isa = PBXBuildFile; productRef = FFDC73C526DFE61F00C1F0D0 /* Focuser */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - FF8D789526E1FC33006287E9 /* swift-focuser */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "swift-focuser"; path = ..; sourceTree = ""; }; + BE6530602B05DD0800922369 /* TextEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditorView.swift; sourceTree = ""; }; + BE6530622B05E2E800922369 /* TextFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldView.swift; sourceTree = ""; }; + BE6530642B05E2FB00922369 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; FFDC739F26DF94DC00C1F0D0 /* focusstate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = focusstate.app; sourceTree = BUILT_PRODUCTS_DIR; }; FFDC73A226DF94DC00C1F0D0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; FFDC73A426DF94DC00C1F0D0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - FFDC73A626DF94DC00C1F0D0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; FFDC73A826DF94DE00C1F0D0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; FFDC73AB26DF94DE00C1F0D0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; FFDC73AE26DF94DE00C1F0D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -33,7 +36,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FFDC73C626DFE61F00C1F0D0 /* Focuser in Frameworks */, + BE65305F2B05D59900922369 /* Focuser in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -43,7 +46,6 @@ FFDC739626DF94DC00C1F0D0 = { isa = PBXGroup; children = ( - FF8D789526E1FC33006287E9 /* swift-focuser */, FFDC73A126DF94DC00C1F0D0 /* focusstate */, FFDC73A026DF94DC00C1F0D0 /* Products */, ); @@ -62,7 +64,9 @@ children = ( FFDC73A226DF94DC00C1F0D0 /* AppDelegate.swift */, FFDC73A426DF94DC00C1F0D0 /* SceneDelegate.swift */, - FFDC73A626DF94DC00C1F0D0 /* ContentView.swift */, + BE6530642B05E2FB00922369 /* RootView.swift */, + BE6530622B05E2E800922369 /* TextFieldView.swift */, + BE6530602B05DD0800922369 /* TextEditorView.swift */, FFDC73A826DF94DE00C1F0D0 /* Assets.xcassets */, FFDC73AD26DF94DE00C1F0D0 /* LaunchScreen.storyboard */, FFDC73B026DF94DE00C1F0D0 /* Info.plist */, @@ -96,7 +100,7 @@ ); name = focusstate; packageProductDependencies = ( - FFDC73C526DFE61F00C1F0D0 /* Focuser */, + BE65305E2B05D59900922369 /* Focuser */, ); productName = focusstate; productReference = FFDC739F26DF94DC00C1F0D0 /* focusstate.app */; @@ -126,7 +130,7 @@ ); mainGroup = FFDC739626DF94DC00C1F0D0; packageReferences = ( - FFDC73C426DFE61F00C1F0D0 /* XCRemoteSwiftPackageReference "swift-focuser" */, + BE65305D2B05D59900922369 /* XCLocalSwiftPackageReference ".." */, ); productRefGroup = FFDC73A026DF94DC00C1F0D0 /* Products */; projectDirPath = ""; @@ -156,8 +160,10 @@ buildActionMask = 2147483647; files = ( FFDC73A326DF94DC00C1F0D0 /* AppDelegate.swift in Sources */, + BE6530632B05E2E800922369 /* TextFieldView.swift in Sources */, FFDC73A526DF94DC00C1F0D0 /* SceneDelegate.swift in Sources */, - FFDC73A726DF94DC00C1F0D0 /* ContentView.swift in Sources */, + BE6530612B05DD0800922369 /* TextEditorView.swift in Sources */, + BE6530652B05E2FB00922369 /* RootView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -298,13 +304,15 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"focusstate/Preview Content\""; + DEVELOPMENT_TEAM = RLK76T8Y89; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = focusstate/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = at.focusstate; + PRODUCT_BUNDLE_IDENTIFIER = org.c61a76ee8f8cc7af.focusstate; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -318,13 +326,15 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"focusstate/Preview Content\""; + DEVELOPMENT_TEAM = RLK76T8Y89; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = focusstate/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = at.focusstate; + PRODUCT_BUNDLE_IDENTIFIER = org.c61a76ee8f8cc7af.focusstate; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -354,21 +364,16 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - FFDC73C426DFE61F00C1F0D0 /* XCRemoteSwiftPackageReference "swift-focuser" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "git@github.com:art-technologies/swift-focuser.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.1.0; - }; +/* Begin XCLocalSwiftPackageReference section */ + BE65305D2B05D59900922369 /* XCLocalSwiftPackageReference ".." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ..; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - FFDC73C526DFE61F00C1F0D0 /* Focuser */ = { + BE65305E2B05D59900922369 /* Focuser */ = { isa = XCSwiftPackageProductDependency; - package = FFDC73C426DFE61F00C1F0D0 /* XCRemoteSwiftPackageReference "swift-focuser" */; productName = Focuser; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4c428c0..ae5c40c 100644 --- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,16 +1,14 @@ { - "object": { - "pins": [ - { - "package": "Introspect", - "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", - "state": { - "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" - } + "pins" : [ + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "state" : { + "revision" : "9e1cc02a65b22e09a8251261cccbccce02731fc5", + "version" : "1.1.1" } - ] - }, - "version": 1 + } + ], + "version" : 2 } diff --git a/Example/focusstate/ContentView.swift b/Example/focusstate/ContentView.swift deleted file mode 100644 index e1a56df..0000000 --- a/Example/focusstate/ContentView.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// ContentView.swift -// focusstate -// -// Created by Augustinas Malinauskas on 01/09/2021. -// - -import SwiftUI -import Focuser - -enum FormFields { - case username, password, name -} - -extension FormFields: FocusStateCompliant { - - static var last: FormFields { - .name - } - - var next: FormFields? { - switch self { - case .username: - return .password - case .password: - return .name - default: return nil - } - } -} - -struct ContentView: View { - @FocusStateLegacy var focusedField: FormFields? - @State var username = "" - @State var password = "" - @State var name = "" - - var body: some View { - VStack{ - TextField("Username", text: $username) - .padding(9) - .background(Color(.systemGray6)) - .cornerRadius(8) - .focusedLegacy($focusedField, equals: .username) - - TextField("Password", text: $password) - .padding(9) - .background(Color(.systemGray6)) - .cornerRadius(8) - .focusedLegacy($focusedField, equals: .password) - - TextField("Name", text: $name) - .padding(9) - .background(Color(.systemGray6)) - .cornerRadius(8) - .focusedLegacy($focusedField, equals: .name) - - - Button(action: { - focusedField = FormFields.password - }) { - Text("Focus Password") - } - } - .padding() - } -} - - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Example/focusstate/RootView.swift b/Example/focusstate/RootView.swift new file mode 100644 index 0000000..1050ecb --- /dev/null +++ b/Example/focusstate/RootView.swift @@ -0,0 +1,28 @@ +// +// RootView.swift +// focusstate +// +// Created by will on 2023/11/16. +// + +import SwiftUI + +struct RootView: View { + var body: some View { + NavigationView { + List { + NavigationLink(destination: TextFieldView()) { + Text("TextField") + } + NavigationLink(destination: TextEditorView()) { + Text("TextEditor") + } + } + .navigationTitle("All Examples") + } + } +} + +#Preview { + RootView() +} diff --git a/Example/focusstate/SceneDelegate.swift b/Example/focusstate/SceneDelegate.swift index 2c1f269..5a513c2 100644 --- a/Example/focusstate/SceneDelegate.swift +++ b/Example/focusstate/SceneDelegate.swift @@ -19,7 +19,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. - let contentView = ContentView() + let contentView = RootView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { diff --git a/Example/focusstate/TextEditorView.swift b/Example/focusstate/TextEditorView.swift new file mode 100644 index 0000000..0b7712a --- /dev/null +++ b/Example/focusstate/TextEditorView.swift @@ -0,0 +1,34 @@ +// +// TextEditorView.swift +// focusstate +// +// Created by will on 2023/11/16. +// + +import Focuser +import SwiftUI + +struct TextEditorView: View { + enum FormFields: FocusStateCompliant { + case textEditor + + static var last: Self { .textEditor } + var next: Self? { nil } + } + + @FocusStateLegacy var focusedField: FormFields? + @State var text = "Edit me!" + + var body: some View { + TextEditor(text: $text) + .padding() + .background(Color(.systemGray6)) + .cornerRadius(20) + .padding() + .focusedLegacy($focusedField, equals: .textEditor) + } +} + +#Preview { + TextEditorView() +} diff --git a/Example/focusstate/TextFieldView.swift b/Example/focusstate/TextFieldView.swift new file mode 100644 index 0000000..9b4839e --- /dev/null +++ b/Example/focusstate/TextFieldView.swift @@ -0,0 +1,68 @@ +// +// TextFieldView.swift +// focusstate +// +// Created by Augustinas Malinauskas on 01/09/2021. +// + +import Focuser +import SwiftUI + +struct TextFieldView: View { + enum FormFields: FocusStateCompliant { + case username, password, name + static var last: FormFields { + .name + } + + var next: FormFields? { + switch self { + case .username: + return .password + case .password: + return .name + default: return nil + } + } + } + + @FocusStateLegacy var focusedField: FormFields? + @State var username = "" + @State var password = "" + @State var name = "" + + var body: some View { + VStack { + TextField("Username", text: $username) + .padding(9) + .background(Color(.systemGray6)) + .cornerRadius(8) + .focusedLegacy($focusedField, equals: .username) + + TextField("Password", text: $password) + .padding(9) + .background(Color(.systemGray6)) + .cornerRadius(8) + .focusedLegacy($focusedField, equals: .password) + + TextField("Name", text: $name) + .padding(9) + .background(Color(.systemGray6)) + .cornerRadius(8) + .focusedLegacy($focusedField, equals: .name) + + Button(action: { + focusedField = FormFields.password + }) { + Text("Focus Password") + } + } + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + TextFieldView() + } +} diff --git a/Package.resolved b/Package.resolved index 4c428c0..45dc25a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -2,12 +2,12 @@ "object": { "pins": [ { - "package": "Introspect", + "package": "swiftui-introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", "state": { "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" + "revision": "9e1cc02a65b22e09a8251261cccbccce02731fc5", + "version": "1.1.1" } } ] diff --git a/Package.swift b/Package.swift index 73ffacb..c7b5fdc 100644 --- a/Package.swift +++ b/Package.swift @@ -11,17 +11,24 @@ let package = Package( products: [ .library( name: "Focuser", - targets: ["Focuser"]), + targets: ["Focuser"] + ), ], dependencies: [ - .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3") + .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", .upToNextMajor(from: "1.1.1")), ], targets: [ .target( name: "Focuser", - dependencies: ["Introspect"]), + dependencies: [ + .product(name: "SwiftUIIntrospect", package: "SwiftUI-Introspect"), + ], + path: "Sources" + ), .testTarget( name: "FocuserTests", - dependencies: ["Focuser"]), + dependencies: ["Focuser"], + path: "Tests" + ), ] ) diff --git a/Sources/Focuser/TextEditorIntrospect.swift b/Sources/Focuser/TextEditorIntrospect.swift index 3b9402f..ae8937c 100644 --- a/Sources/Focuser/TextEditorIntrospect.swift +++ b/Sources/Focuser/TextEditorIntrospect.swift @@ -1,26 +1,29 @@ // // SwiftUIView.swift -// +// // // Created by Augustinas Malinauskas on 13/09/2021. // import SwiftUI +import SwiftUIIntrospect public struct FocusModifierTextEditor: ViewModifier { @Binding var focusedField: Value? var equals: Value @State var observer = TextFieldObserver() - + public func body(content: Content) -> some View { content - .introspectTextView { tv in - if focusedField == equals { + .introspect(.textEditor, on: .iOS(.v14, .v15, .v16, .v17)) { tv in + if focusedField == equals, + tv.isFirstResponder == false + { tv.becomeFirstResponder() } } .simultaneousGesture(TapGesture().onEnded { - focusedField = equals + focusedField = equals }) } } diff --git a/Sources/Focuser/TextFieldIntrospect.swift b/Sources/Focuser/TextFieldIntrospect.swift index daf78ec..1af7254 100644 --- a/Sources/Focuser/TextFieldIntrospect.swift +++ b/Sources/Focuser/TextFieldIntrospect.swift @@ -1,17 +1,17 @@ // // File.swift -// +// // // Created by Augustinas Malinauskas on 01/09/2021. // import SwiftUI -import Introspect +import SwiftUIIntrospect class TextFieldObserver: NSObject, UITextFieldDelegate { - var onReturnTap: () -> () = {} + var onReturnTap: () -> Void = {} weak var forwardToDelegate: UITextFieldDelegate? - + @available(iOS 2.0, *) func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { forwardToDelegate?.textFieldShouldBeginEditing?(textField) ?? true @@ -41,7 +41,7 @@ class TextFieldObserver: NSObject, UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { forwardToDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } - + @available(iOS 13.0, *) func textFieldDidChangeSelection(_ textField: UITextField) { forwardToDelegate?.textFieldDidChangeSelection?(textField) @@ -51,7 +51,7 @@ class TextFieldObserver: NSObject, UITextFieldDelegate { func textFieldShouldClear(_ textField: UITextField) -> Bool { forwardToDelegate?.textFieldShouldClear?(textField) ?? true } - + @available(iOS 2.0, *) func textFieldShouldReturn(_ textField: UITextField) -> Bool { onReturnTap() @@ -63,15 +63,15 @@ public struct FocusModifier: ViewModifier @Binding var focusedField: Value? var equals: Value @State var observer = TextFieldObserver() - + public func body(content: Content) -> some View { content - .introspectTextField { tf in + .introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { tf in if !(tf.delegate is TextFieldObserver) { observer.forwardToDelegate = tf.delegate tf.delegate = observer } - + /// when user taps return we navigate to next responder observer.onReturnTap = { focusedField = focusedField?.next ?? Value.last @@ -83,13 +83,15 @@ public struct FocusModifier: ViewModifier } else { tf.returnKeyType = .next } - - if focusedField == equals { + + if focusedField == equals, + tf.isFirstResponder == false + { tf.becomeFirstResponder() } } .simultaneousGesture(TapGesture().onEnded { - focusedField = equals + focusedField = equals }) } }