I am capture the return key button pressed even in onSubmit. Switching state in this way, dismisses the keyboard, but presents it immediately again. So they keyboard doesn't fully dismiss, it just dips.
How do I prevent the keyboard dipping? I want it to remain on screen as I switch the focused field to another.
enum Field {
case first, second
}
struct ContentView: View {
#State private var content1 = ""
#State private var content2 = ""
#FocusState private var state: Field?
var body: some View {
VStack {
TextField("Hi1", text: $content1)
.focused($state, equals: .first)
TextField("Hi2", text: $content2)
.focused($state, equals: .second)
}
.onSubmit {
switch state {
case .none:
break
case .some(let wrapped):
switch wrapped {
case .first:
state = .second
case .second:
state = nil
}
}
}
.padding()
}
}
Related
I've put a picker inside a menu so I can apply custom text and images in the future. It seems that, on a new app launch, the menu only gets updated on a second tap though. On the first tap the picker check-mark gets an update, however neither the ViewModel nor the menu change until a second tap. Any ideas on how to have all the components work every time?
class ViewModel: ObservableObject {
#Published var soundSelection: String = "Off"
#Published var soundSelectionPickerValue: Int = 0
func updateSoundSelection(to: Int) {
var updatedSoundSelection: String
switch to {
case 0:
updatedSoundSelection = "off"
case 1:
updatedSoundSelection = "chime"
case 2:
updatedSoundSelection = "alert"
case 3:
updatedSoundSelection = "peaceful"
default:
return
}
soundSelection = updatedSoundSelection
}
}
struct ContentView: View {
#StateObject var viewModel: ViewModel
var body: some View {
VStack {
Menu {
Picker("", selection: $viewModel.soundSelectionPickerValue) {
Text("off").tag(0)
Text("chime").tag(1)
Text("alert").tag(2)
Text("peaceful").tag(3)
}
.onChange(of: viewModel.soundSelectionPickerValue) { tag in
viewModel.updateSoundSelection(to: tag) }
} label: {
VStack {
Text(viewModel.soundSelection)
}
}
}
}
}
In SwiftUI 4, there is now a NavigationSplitView. I played around with it and detected some strange behaviour.
Consider the following code: When the content function returns the plain Text, then there is the expected behaviour - tapping a menu item changes the detail view to the related text.
However, when commenting out the first four cases, and commenting in the next four, then a tap on "Edit Profile" does not change the detail view display. (Using #ViewBuilder does not change this behaviour.)
Any ideas out there about the reasons for that? From my point of view, this may just be a simple bug, but perhaps there are things to be considered that are not documented yet?!
struct MainScreen: View {
#State private var menuItems = MenuItem.menuItems
#State private var menuItemSelection: MenuItem?
var body: some View {
NavigationSplitView {
List(menuItems, selection: $menuItemSelection) { course in
Text(course.name).tag(course)
}
.navigationTitle("HappyFreelancer")
} detail: {
content(menuItemSelection)
}
.navigationSplitViewStyle(.balanced)
}
func content(_ selection: MenuItem?) -> some View {
switch selection {
case .editProfile:
return Text("Edit Profile")
case .evaluateProfile:
return Text("Evaluate Profile")
case .setupApplication:
return Text("Setup Application")
case .none:
return Text("none")
// case .editProfile:
// return AnyView(EditProfileScreen())
//
// case .evaluateProfile:
// return AnyView(Text("Evaluate Profile"))
//
// case .setupApplication:
// return AnyView(Text("Setup Application"))
//
// case .none:
// return AnyView(Text("none"))
}
}
}
struct MainScreen_Previews: PreviewProvider {
static var previews: some View {
MainScreen()
}
}
enum MenuItem: Int, Identifiable, Hashable, CaseIterable {
var id: Int { rawValue }
case editProfile
case evaluateProfile
case setupApplication
var name: String {
switch self {
case .editProfile: return "Edit Profile"
case .evaluateProfile: return "Evaluate Profile"
case .setupApplication: return "Setup Application"
}
}
}
extension MenuItem {
static var menuItems: [MenuItem] {
MenuItem.allCases
}
}
struct EditProfileScreen: View {
var body: some View {
Text("Edit Profile")
}
}
After playing around a bit in order to force SwiftUI to redraw the details view, I succeeded in this workaround:
Wrap the NavigationSplitView into a GeometryReader.
Apply an .id(id) modifier to the GeometryReader (e.g., as #State private var id: Int = 0)
In this case, any menu item selection leads to a redraw as expected.
However, Apple should fix the bug, which it is obviously.
I've found that wrapping the Sidebar list within its own view will fix this issue:
struct MainView: View {
#State var selection: SidebarItem? = .none
var body: some View {
NavigationSplitView {
Sidebar(selection: $selection)
} content: {
content(for: selection)
} detail: {
Text("Detail")
}
}
#ViewBuilder
func content(for item: SidebarItem?) -> some View {
switch item {
case .none:
Text("Select an Item in the Sidebar")
case .a:
Text("A")
case .b:
Text("B")
}
}
}
I have a form with a number of fields and have set up the focused modifier to allow the user to move through the form fields:
struct CustomerForm: View {
#ObservedRealmObject var customer: Customer
// ...
#FocusState private var focusedField: Field?
enum Field: Hashable {
case nameFirst
case nameLast
case notes
case phone
case email
case addrCity
case addrStreet1
case addrStreet2
case addrPostalCode
}
var body: some View {
NavigationView {
Form {
// ...
TextField(
"City",
text: $customer.addrCity,
onCommit: { focusedField = .addrPostalCode }
)
.focused($focusedField, equals: .addrCity)
.submitLabel(.next)
TextField(
"Postal Code",
text: $customer.addrPostalCode,
)
.focused($focusedField, equals: .addrPostalCode)
.onChange(of: customer.addrPostalCode, perform: { value in
// ...
})
Currently the onChange observer is fired each time I make a change to $customer.addrPostalCode and need it to fire when the focus is lost.
I understand I can do that with a separate #FocusState wrapper:
#FocusState private var isFocused: Bool
// ...
var body: some View {
Form {
TextField("Name", text: $name)
.focused($isFocused)
.onChange(of: isFocused) { isFocused in
// ...
}
}
}
but then I loose the ability to move through the fields with the Field enum.
Is it possible to use the onChange observer with my Field enum?//
I have a textfield to enter search terms, plus a segmented control to indicate what database table should be searched:
struct ContentView: View {
enum SearchMode: Int {
case searchInNumbers, searchInAlphabet
}
#State private var mode = SearchMode.searchInNumbers
#State private var searchString = ""
private var keyboardType: UIKeyboardType {
if self.mode == .searchInNumbers {
NSLog("Setting keyboard to numbersAndPunctuation")
return .numbersAndPunctuation
} else {
NSLog("Setting keyboard to default")
return .default
}
}
var body: some View {
VStack {
TextField("Search string", text: self.$searchString)
.keyboardType(self.keyboardType)
.textFieldStyle(RoundedBorderTextFieldStyle())
Picker("Search in", selection: self.makePickerBinding()) {
Text("numbers").tag(0)
Text("alphabet").tag(1)
}.pickerStyle(SegmentedPickerStyle())
}.padding(20)
}
private func makePickerBinding() -> Binding<Int> {
Binding<Int>(
get: {
return self.mode.rawValue
},
set: { tag in
self.mode = SearchMode.init(rawValue: tag)!
}
)
}
}
The keyboard style should be the right one for the database table we search. This works, except when the keyboard is already up.
Meaning, when the user first taps the textfield, and then changes the segmented control, the keyboard type does not change.
I don't want to fix it with yet another wrapped UITextField. Can it be fixed in SwiftUI?
I experience some weird behavior since updating to Xcode 12 / iOS 14. To verify this behavior, I isolated it in a test app.
In my original app, I have multiple Sheets to control. This is why I have en enum to differentiate between the currently active sheet and I store this in a #State private var activeSheet. In the #ViewBuilder function sheetContent() I return the View for the selected Sheet.
For this test app, I only implemented one single sheet for simplicity reasons.
Here's the ContentView's code:
struct ContentView: View {
#State private var showingSheet = false
#State private var activeSheet = ContentViewSheets.State.none
var body: some View {
Button(action: {
activeSheet = .aSheet
showingSheet = true // <-- ISSUE: activeSheet is still set to .none here!
}) {
Text("Open Sheet")
}
.sheet(isPresented: $showingSheet, content: sheetContent)
}
#ViewBuilder
private func sheetContent() -> some View {
switch activeSheet {
case .aSheet:
Text("I'm the right sheet!")
case .none:
Text("Oops! I'm not supposed to show up!")
}
}
}
This is the code for the enum:
class ContentViewSheets {
enum State {
case aSheet
case none
}
}
As commented in the ContentView code above, the value of the activeSheet property is never changed to .aSheet, but remains .none - so that the wrong sheet will be presented.
Is this a bug or didn't I properly understand Apple's #State? They just write in the documentation that it shall not be used in initializer, but only within the body, which I definitely do.
Instead of using two states, use one and explicitly designed sheet modifier for such scenarios.
Tested with Xcode 12 / iOS 14
class ContentViewSheets {
enum State: Identifiable {
case aSheet
case none
var id: State { self }
}
}
struct ContentView: View {
#State private var activeSheet: ContentViewSheets.State?
var body: some View {
Button(action: {
activeSheet = .aSheet
}) {
Text("Open Sheet")
}
.sheet(item: $activeSheet) {
sheetContent($0) // << activate state passed here !!!
}
}
#ViewBuilder
private func sheetContent(_ state: ContentViewSheets.State) -> some View {
switch state {
case .aSheet:
Text("I'm the right sheet!")
default:
Text("Oops! I'm not supposed to show up!")
}
}
}