Post

Replies

Boosts

Views

Activity

Predictive text / suggest next word in sentence on iOS?
On an iOS device, using the native keyboard (US english and "Predictive" turned on), if you write eg:"Have a " (with a space at the end)It will suggest three contextually relevant next-word-predictions on the bar at the top of the keyboard:good great niceI want to make use of this next-word prediction logic on iOS, but it turns out that I can only get it to work on OS X, where it's super simple.So here is a tiny command line app that does exactly what I want (allthough only on OS X):import AppKit let str = "Have a " let rangeForEndOfStr = NSMakeRange(str.utf16.count, 0) let spellChecker = NSSpellChecker.sharedSpellChecker() let completions = spellChecker.completionsForPartialWordRange( rangeForEndOfStr, inString: str, language: "en", inSpellDocumentWithTag: 0) print(completions)Running that program will print:Optional(["good", "great", "nice", "lot", "problem", "new", "feeling", "chance", "few", "wonderful", "look", "big", "boyfriend", "better", "very", "job", "bad", "lovely", "crush", "blessed"])Note that the first three words are exactly the same as those displayed by the native (predictive) iOS keyboard. So it works (at least on OS X)!(It works by giving .completionsForPartialWordRange an zero-length range located at the end of the string (where the next word would go), rather than a range containing a partial word, allthough I guess you could say that a non-existing word is also a partial word.)But trying the same approach on iOS (using UITextChecker instead of NSSpellChecker, etc.) does not work:let str = "Have a " let rangeForEndOfStr = NSMakeRange(str.utf16.count, 0) let spellChecker = UITextChecker() print(UITextChecker.availableLanguages()) let completions = spellChecker.completionsForPartialWordRange( rangeForEndOfStr, inString: str, language: "en_US") // ( <-- .availableLanguages() says "en_US" on iOS and "en" on OS X. ) print(completions)(this code can be put in the viewDidLoad of the ViewController of an otherwise empty Single View iOS app.)Run the iOS app and it will just print nil. : (Turns out that UITextChecker's .completionsForPartialWordRange (contrary to NSSpellChecker's) simply returns nil if the range has zero length. I have tried all day to find any other way to get next-word-of-sentence-predictions/-suggestions/-completions on iOS but failed.(NOTE: I have no problem getting UITextChecker to return completions of partially entered wods, ie where the range is not empty, but contains partially written word(prefixe)s, allthough the resulting completions are not sorted so that the more probable comes first, as the documentation says. They are actually just sorted alphabetically ... Anyway, what I want is something else: Given a partially written sentence containing only complete words, I want a list of probable-next-word-in-sentence-completions, exactly as examplified by my OS X example and the native iOS keyboard above.)So, how do I write a working iOS-version of the OS X example above?
10
1
5.6k
May ’16
Xcode Previews bug? View is not re-rendered as expected
I've made a small reproducing example app to demonstrate this issue. Just create an iOS App project and replace the App and ContentView files with the following code. The app works as expected when running it in a simulator or on a device but the SwiftUI Preview will get stuck showing only the loading state, even though the print statement in the View's body has printed viewModel.state: showingContent(SwiftUITest.ViewModel.Content(text: "Hello world!", reloadButtonTitle: "Reload"))to the console. When stuck showing the loading state (an animated ProgressView), If I change .padding(.all, 12) to e.g. .padding(.all, 2) or vice versa, then that will make the Preview render the expected content. Also, if I tap the Reload-button, it will not show the ProgressView for 2 seconds as expected (and as the app will do when run in a simulator or on a device), instead it will just show a white screen, and the same workaround (changing the padding amount) can be used to make the it render the expected content. Can other people reproduce this behavior, is it a known bug, or am I doing something wrong? TestApp.swift import SwiftUI @main struct SwiftUITestApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: ViewModel( contentLoader: { try! await Task.sleep(nanoseconds: 2_000_000_000) return .init(text: "Hello world!", reloadButtonTitle: "Reload") } )) } } } ContentView.swift import SwiftUI struct ContentView: View { @StateObject var viewModel: ViewModel var body: some View { let _ = print("viewModel.state:", viewModel.state) Group { switch viewModel.state { case .notStarted, .loading: ProgressView() case .showingContent(let content): VStack { Text(content.text) .padding(.all, 12) Button(content.reloadButtonTitle) { viewModel.handle(event: .reloadButtonWasTapped) } } } } .onAppear { viewModel.handle(event: .viewDidAppear) } } } // MARK: - ViewModel @MainActor final class ViewModel: ObservableObject { @Published var state: State = .notStarted let contentLoader: () async -> Content init(contentLoader: @escaping () async -> Content) { self.contentLoader = contentLoader } func handle(event: Event) { switch state { case .notStarted: if event == .viewDidAppear { loadContent() } case .loading: break case .showingContent: if event == .reloadButtonWasTapped { loadContent() } } } func loadContent() { guard state != .loading else { return } state = .loading Task { print("starts loading", Date.now) let content = await contentLoader() print("finished loading", Date.now) state = .showingContent(content) } } enum State: Equatable { case notStarted case loading case showingContent(Content) } struct Content: Equatable { let text: String let reloadButtonTitle: String } enum Event: Equatable { case viewDidAppear case reloadButtonWasTapped } } // MARK: - Preview struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: ViewModel( contentLoader: { try! await Task.sleep(nanoseconds: 2_000_000_000) return .init(text: "Hello world!", reloadButtonTitle: "Reload") } )) } } Here's the simple behavior of the app (recorded from simulator): Each time the view appears, it loads it's content for two seconds while showing a ProgressView, then it shows the content, which includes a Reload button, and if you tap that button it will reload the content for 2 seconds again. I would expect the Preview to behave in the same way.
2
0
1.2k
Sep ’23
Picker value resets when in a sheet, but works fine in parent View
In the attached minimal reproducing app, the picker in the List works as expected while the one in the sheet resets every time the List view updates (this demo app displays a random byte value that is updated every second). Can anyone explain why this happens and how to solve the issue, i.e. how can I have a picker in a sheet on a view without the picker getting reset when the parent view receives updates? Somebody asked the same question here, but it is unanswered, it has different example code: https://www.hackingwithswift.com/forums/swiftui/picker-value-resets-when-in-a-sheet-but-works-fine-in-parent-view/17211 import SwiftUI @main struct SwiftUITestApp: App { var body: some Scene { WindowGroup { ContentView() } } } // MARK: - ContentView struct ContentView: View { @State var selectedValue: Int = 0 @State var isSheetPresented: Bool = false @State var randomByte: UInt8 = 0 var body: some View { List { Text("Frequently updated random byte: \(randomByte)") Text("Note how the picker works as expected here:") MyPicker() Text("But not within a sheet:") Button("Select a value inside a sheet") { isSheetPresented = true } } .sheet( isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false }, content: { List { VStack(alignment: .leading, spacing: 16) { Text("Select a value close to the bottom of the drop down menu.") Text("Then open the drop down menu again.") Text("Note that the drop down menu scrolls each time `randomByte` changes.") } MyPicker() } } ) .task { while true { try! await Task.sleep(nanoseconds: 1_000_000_000) randomByte = UInt8.random(in: .min ... .max) } } } } // MARK: MyPicker struct MyPicker: View { @State var selectedValue: Int = 0 var body: some View { Picker("Value", selection: $selectedValue) { ForEach(0..<100, id: \.self) { value in Text("\(value)").tag(value) } } .onChange(of: selectedValue) { _ in print("•••• selectedValue", selectedValue) } } } // MARK: Preview struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
2
0
601
Sep ’23