Bug while trying to open DatePicker when Picker is opened - ios

I wanted to open DatePicker when Picker is already opened, but result of it is that DatePicker is acting as opened (it label is blue colored), but nothing is presented and also while trying to again click on DatePicker it is not doing anything.
I created some MRE -
enum SomeCases: String, CaseIterable, Identifiable {
case first = "First"
case second = "Second"
case third = "Third"
case fourth = "Fourth"
public var id: String { rawValue }
}
struct SwiftUIView: View {
#State private var someCases: SomeCases = .first
#State private var someDate: Date = .init()
var body: some View {
VStack (spacing: 150){
Picker("First picker", selection: $someCases) {
ForEach(SomeCases.allCases) { someCase in
Text(someCase.rawValue)
.fontWeight(.bold)
}
}
DatePicker("Date picker", selection: $someDate)
}
.padding()
}
}
Also got this message -
[Presentation] Attempt to present <_UIDatePickerContainerViewController: 0x7f78f8b049b0> on <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x7f78f301b800> (from <_TtGC7SwiftUI19UIHostingControllerVVS_P10$11d30daf821BridgedNavigationView8RootView_: 0x7f78f301aa00>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x7f78f399d810>.
Tried to look for some answers online, but there is always solution with using .sheet, which I don't want to use here. Also it is iOS 15.

Related

SwiftUI Form: Sheet isn't dismissed automatically when the view it's attached to is gone

See the code below (BTW, List has the same issue):
enum Value: String, Equatable {
case a = "a"
case b = "b"
}
struct ContentView: View {
#State var value: Value = .a
#State var showSheet = false
var body: some View {
Form {
Section {
switch value {
case .a:
Text("a")
.onTapGesture {
showSheet = true
}
.sheet(isPresented: $showSheet) {
Button("Set Value to .b") {
value = .b
}
}
case .b:
Text("b")
}
}
}
}
}
To reproduce the issue, first click on text 'a' to bring up a sheet, then click on button in the sheet to change value. I'd expect the sheet should be dismissed automatically, because the view it's attached to is gone. But it isn't.
If I remove the Form, or replace it with, say, ScrollView, the behavior is as I expected. On other other hand, however, replacing it with List has the same issue (sheet isn't dismissed).
I'm thinking to file a FB, but would like to ask here first. Does anyone know if it's a bug or a feature? If it's a feature, what's the rationale behind it?
I'm currently using this workaround. Any other suggestions other than calling dismiss() in button handler are appreciated.
.onChange(of: value) { _ in
showSheet = false
}
UPDATE: I think what I really wanted to ask is if there is an "ownership" concept for sheet? I mean:
Does a sheet belongs to the view it's attached to and hence get dismissed when that view is destroyed?
Or it doesn't matter where a sheet is defined in view hierarchy?
Since I never read about the ownership concept, I suppose item 2 is true. But if so, I don't understand why the sheet is dismissed automatically when I removed Form and Section in the above code.
Try this approach of adding showSheet = false just after value = .b in your sheet Button. Also move your .sheet(...) outside the Form, and especially if you are using a List.
struct ContentView: View {
#State var value: Value = .a
#State var showSheet = false
var body: some View {
Form {
Section {
switch value {
case .a:
Text("a")
.onTapGesture {
showSheet = true
}
case .b:
Text("b")
}
}
}
.sheet(isPresented: $showSheet) { // <-- here
Button("Set Value to .b") {
value = .b
showSheet = false // <-- here
}
}
}
}

Dynamically setting sheet to present with button [duplicate]

I have two Modal/Popover .sheet's I would like to show based on which button is pressed by a user. I have setup an enum with the different choices and set a default choice.
Expected behaviour:
When the user selects any choice, the right sheet is displayed. When the user THEN selects the other choice, it also shows the correct sheet.
Observed behaviour:
In the example below, when the user first picks the second choice, the first sheet is shown and will continue to show until the user selects the first sheet, then it will start to switch.
Debug printing shows that the #State variable is changing, however, the sheet presentation does not observe this change and shows the sheets as described above. Any thoughts?
import SwiftUI
//MARK: main view:
struct ContentView: View {
//construct enum to decide which sheet to present:
enum ActiveSheet {
case sheetA, sheetB
}
//setup needed vars and set default sheet to show:
#State var activeSheet = ActiveSheet.sheetA //sets default sheet to Sheet A
#State var showSheet = false
var body: some View {
VStack{
Button(action: {
self.activeSheet = .sheetA //set choice to Sheet A on button press
print(self.activeSheet) //debug print current activeSheet value
self.showSheet.toggle() //trigger sheet
}) {
Text("Show Sheet A")
}
Button(action: {
self.activeSheet = .sheetB //set choice to Sheet B on button press
print(self.activeSheet) //debug print current activeSheet value
self.showSheet.toggle() //trigger sheet
}) {
Text("Show Sheet B")
}
}
//sheet choosing view to display based on selected enum value:
.sheet(isPresented: $showSheet) {
switch self.activeSheet {
case .sheetA:
SheetA() //present sheet A
case .sheetB:
SheetB() //present sheet B
}
}
}
}
//MARK: ancillary sheets:
struct SheetA: View {
var body: some View {
Text("I am sheet A")
.padding()
}
}
struct SheetB: View {
var body: some View {
Text("I am sheet B")
.padding()
}
}
With some very small alterations to your code, you can use sheet(item:) for this, which prevents this problem:
//MARK: main view:
struct ContentView: View {
//construct enum to decide which sheet to present:
enum ActiveSheet : String, Identifiable { // <--- note that it's now Identifiable
case sheetA, sheetB
var id: String {
return self.rawValue
}
}
#State var activeSheet : ActiveSheet? = nil // <--- now an optional property
var body: some View {
VStack{
Button(action: {
self.activeSheet = .sheetA
}) {
Text("Show Sheet A")
}
Button(action: {
self.activeSheet = .sheetB
}) {
Text("Show Sheet B")
}
}
//sheet choosing view to display based on selected enum value:
.sheet(item: $activeSheet) { sheet in // <--- sheet is of type ActiveSheet and lets you present the appropriate sheet based on which is active
switch sheet {
case .sheetA:
SheetA()
case .sheetB:
SheetB()
}
}
}
}
The problem is that without using item:, current versions of SwiftUI render the initial sheet with the first state value (ie sheet A in this case) and don't update properly on the first presentation. Using this item: approach solves the issue.

Segmented picker not letting me pick a different selection

I have a segmented picker with three options that defaults to the first option. The problem is that when I select any of the other ones, it will not change for some reason.
This is what it looks like.
#Binding var transaction: TransactionModel
var body: some View {
VStack {
DatePicker(
"Transaction Date",
selection: $transaction.date,
displayedComponents: [.date]
)
Divider()
Picker("Type", selection: $transaction.type) {
ForEach(TransactionModel.TransactionType.allCases, id:\.self) { tType in
Text(tType.rawValue.capitalized)
}
}.pickerStyle(SegmentedPickerStyle())
}
.padding()
}
This is the code for the view so far, and below is the code for TransactionModel.
struct TransactionModel {
var date = Date()
var type = TransactionType.income
static let `default` = TransactionModel()
enum TransactionType: String, CaseIterable, Identifiable {
case income = "Income"
case expense = "Expense"
case transfer = "Transfer"
var id: String {self.rawValue}
}
}
One thing to note, it will work with the inline picker style. That will let me change between those three options, however, I want to use the segmented one. Can anyone help me figure out what's going on with this?
Edit: I actually found that the inline picker style doesn't seem to be working either. I added some code to display a text view based on what was selected, and it never changed from what it said when Income was selected. But that could be due to my code itself. Below is the code for that.
Picker("Type", selection: $transaction.type) {
ForEach(TransactionModel.TransactionType.allCases, id:\.self) { tType in
Text(tType.rawValue.capitalized)
}
}.pickerStyle(InlinePickerStyle())
Divider()
if(transaction.type == TransactionModel.TransactionType.income) {
Text("Income Selected")
Divider()
}else if(transaction.type == TransactionModel.TransactionType.expense) {
Text("Expense Selected")
Divider()
}

iOS14 introducing errors with #State bindings

The below swiftUI code was working fine with iOS13, but on testing with iOS14, I'm getting fatal errors caused by the force-unwrapped optional when trying to display the modal sheet. As far as I can tell, the sheet should never try to present with a nil value for selectedModel, as showingDetails is only ever made true after assigning selectedModel?
struct SpeakerBrandMenu: View {
var filteredSpeakers: [Speaker] {
// An array of Speaker objects
}
#State var selectedModel: Speaker?
#State private var showingDetails = false
var body: some View {
List{
ForEach(filteredSpeakers) { speaker in
HStack {
Button(action: {
self.selectedModel = speaker
self.showingDetails = true
}) {
SpeakerModelRow(speaker: speaker).contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
Spacer()
Button(
//unrelated
).padding(5)
}
}
} .sheet(isPresented: self.$showingDetails) { SpeakerDetailView(speaker: self.selectedModel!, showSheet: self.$showingDetails).environmentObject(self.favoriteSpeakers).environmentObject(self.settings)}
.navigationBarTitle(Text(brand), displayMode: .inline)
}
}
Interestingly, if I unwrap it as speaker: self.selectedModel ?? filteredSpeakers[0]it behaves exactly as expected: The first time pressing any of the menu items, the first item is passed to the sheet, but on dismissing the sheet and selecting another item it then shows the correct item every time. So it's as though the button to assign selectedModel is trying to display the sheet before it has had time assign it.
It looks like in iOS 14 the sheet(isPresented:content:) is now created beforehand, so any changes made to selectedModel are ignored.
Try using sheet(item:content:) instead:
var body: some View {
List {
...
}
.sheet(item: self.$selectedModel) {
SpeakerDetailView(speaker: $0)
}
}
and dismiss the sheet using #Environment(\.presentationMode):
struct SpeakerDetailView: View {
#Environment(\.presentationMode) private var presentationMode
var speaker: Speaker
var body: some View {
Text("Speaker view")
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
}

SwiftUI - TextField is disabled (not editable) when placed in List on macOS

TextField is disabled (not editable) when placed in List on macOS. When the same code is build for iOS and ran in Simulator, it works as expected.
Is this a bug, or am I missing something?
The code:
struct ContentView : View {
#State private var text: String = ""
var body: some View {
VStack {
List {
// TextField is not editable when this code is ran on macOS
TextField($text, placeholder: Text("Entry text"))
Text("Entered text: \(text)")
}
// TextField is editable on both macOS as well as iOS
TextField($text, placeholder: Text("Entry text"))
}
}
}
That's because the list is taking clicks to drive selection, which you're not using here. TextField becomes editable in the list on macOS only when the row in which it is housed has selection.
If you change your code to something like this
struct ContentView : View {
#State private var text: String = "Hello"
#State private var selection: Int? = nil
var body: some View {
VStack {
List(selection: $selection) {
ForEach(0..<5) { _ in
TextField(self.$text)
}
}
TextField($text)
}
}
}
then run the code, the first click on the cell will cause it to get selected and the second click will cause the text field to receive focus.
Create the TextField using the following code
struct ContentView : View {
#State private var helloText: String = "Hello"
#State private var selection: Int? = nil
var body: some View {
VStack {
List(selection: $selection) {
ForEach(0..<5) { _ in
TextField(.constant(self.helloText), placeholder: Text("Entry text")).textFieldStyle(.roundedBorder)
}
}
}
}
}

Resources