As soon as I use a code, my MacBook heats up to 50 degrees. I have 4 Enums that each have a picker, as soon as I put 3 pickers into an if-else statement and then debug, my MacBook heats up so much that I almost do it can not touch anymore.
error ->
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
i use the enums with the protocols Int16, Hashable, CaseIterable.
i want Int16 for my CoreData Attribut.
If i change Int16 to Int then the Code Works..
i Convert in the "Taggs" to int so i can use the else if statemant.
Import SwiftUI
struct EnumsInEnums: View {
#State var pickMain = 0
#State var pickA = 0
#State var pickB = 0
#State var pickC = 0
enum MainEnum : Int16, Hashable, CaseIterable
{
case AEnum
case BEnum
case CEnum
var name: String { return "\(self)" }
}
enum AEnum : Int16, Hashable, CaseIterable
{
case Aa
case Ba
case Ca
var name: String { return "\(self)" }
}
enum BEnum : Int16, Hashable, CaseIterable
{
case Ab
case Bb
case Cb
var name: String { return "\(self)" }
}
enum CEnum : Int16, Hashable, CaseIterable
{
case Ac
case Bc
case Cc
var name: String { return "\(self)" }
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $pickMain, label: Text("Pick \(pickMain)")) {
ForEach(MainEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
if pickMain == 0 {
Picker(selection: $pickA, label: Text("Pick \(pickA)")) {
ForEach(AEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
} else if pickMain == 1 {
Picker(selection: $pickB, label: Text("Pick \(pickB)")) {
ForEach(BEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
} else if pickMain == 2 {
Picker(selection: $pickC, label: Text("Pick \(pickC)")) {
ForEach(CEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
}
}
}
}
}
}
I expect to select the Main and then the second picker will show me the selected, as if I would select an automaker and in the second picker come the respective car model.
In beta 5, there is a known problem with ForEach. From the release notes:
Using a ForEach view with a complex expression in its closure can may
result in compiler errors. Workaround: Extract those expressions into
their own View types. (53325810)
Your ForEach views, do not seem too complex, and encapsulating its contents does not solve the problem. However, if you encapsulate the entire pickers works fine. Double check the code, as I may have introduced a typo while encapsulating.
import SwiftUI
struct ContentView: View {
var body: some View {
EnumsInEnums()
}
}
struct EnumsInEnums: View {
#State var pickMain = 0
#State var pickA = 0
#State var pickB = 0
#State var pickC = 0
enum MainEnum : Int16, Hashable, CaseIterable
{
case AEnum
case BEnum
case CEnum
var name: String { return "\(self)" }
}
enum AEnum : Int16, Hashable, CaseIterable
{
case Aa
case Ba
case Ca
var name: String { return "\(self)" }
}
enum BEnum : Int16, Hashable, CaseIterable
{
case Ab
case Bb
case Cb
var name: String { return "\(self)" }
}
enum CEnum : Int16, Hashable, CaseIterable
{
case Ac
case Bc
case Cc
var name: String { return "\(self)" }
}
var body: some View {
NavigationView {
Form {
Section {
PickerMain(pickMain: $pickMain)
if pickMain == 0 {
PickerA(pickA: $pickA)
}
else if pickMain == 1 {
PickerB(pickB: $pickB)
} else if pickMain == 2 {
PickerC(pickC: $pickC)
}
}
}
}
}
struct PickerMain: View {
#Binding var pickMain: Int
var body: some View {
Picker(selection: $pickMain, label: Text("Pick \(pickMain)")) {
ForEach(MainEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
}
}
struct PickerA: View {
#Binding var pickA: Int
var body: some View {
Picker(selection: $pickA, label: Text("Pick \(pickA)")) {
ForEach(AEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
}
}
struct PickerB: View {
#Binding var pickB: Int
var body: some View {
Picker(selection: $pickB, label: Text("Pick \(pickB)")) {
ForEach(BEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
}
}
struct PickerC: View {
#Binding var pickC: Int
var body: some View {
Picker(selection: $pickC, label: Text("Pick \(pickC)")) {
ForEach(CEnum.allCases, id: \.self){ x in
Text("\(x.name)").tag(Int(x.rawValue))
}
}
}
}
}
Related
I have a List, with custom Stepper inside each row. Therefore, when I scroll my stepper is reset. (The increment and decrement works when is visible. When it disappear, it's reset. Don't keep the state. It's alway's reset).
Xcode: v14.2 / Simulator iOS: 16.2
struct Product: Codable, Hashable, Identifiable {
let id: String
let name: String
let step: Int
let quantity: Int
let priceHT: Double
}
class ProductViewModel: ObservableObject {
#Published var products = [Product]()
...
}
struct ProductListView: View {
#EnvironmentObject var productViewModel: ProductViewModel
var body: some View {
List(productViewModel.products) { product in
ProductRowView(product: product)
}
}
}
My List row:
I tried to modify #State with #binding, but without success.
struct ProductRowView: View {
#State var product: Product
var body: some View {
HStack {
VStack {
Text(product.name)
Text(String(format: "%.2f", product.priceHT) + "€ HT")
}
Spacer()
MyStepper(product: $product, value: product.quantity)
.font(.title)
}
}
}
My Custom stepper:
struct MyStepper: View {
#Binding var product: Product
#State var value: Int = 0
var body: some View {
HStack {
VStack() {
HStack {
Button(action: {
value -= product.step
if let row = orderViewModel.productsOrder.firstIndex(where: { $0.name == product.name }) {
let order = Product(id: product.id, name: product.name, step: product.step, quantity: value, priceHT: product.priceHT)
if (value == 0) {
orderViewModel.productsOrder.remove(at: row)
} else {
orderViewModel.productsOrder[row] = order
}
}
}, label: {
Image(systemName: "minus.square.fill")
})
Text(value.formatted())
Button(action: {
value += product.step
let order = Product(id: product.id, name: product.name, step: product.step, quantity: value, priceHT: product.priceHT)
if let row = orderViewModel.productsOrder.firstIndex(where: { $0.name == product.name }) {
orderViewModel.productsOrder[row] = order
} else {
orderViewModel.productsOrder.append(order)
}
}, label: {
Image(systemName: "plus.app.fill")
})
}
Text(product.unit)
}
}
}
}
Thks
EDIT / RESOLVED
Here is the solution for my case :
Change type of quantity. let to var
struct Product: Codable, Hashable, Identifiable {
...
var quantity: Int
...
}
Delete #State in MyStepper and replace value by product.quantity
Use bindings for that, e.g.
struct ProductListView: View {
#EnvironmentObject var model: Model
var body: some View {
List($model.products) { $product in
ProductRowView(product: $product)
}
}
}
struct ProductRowView: View {
#Binding var product: Product // now you have write access to the Product struct
...
However, to make the View reusable and to help with previewing, it's best to pass in only the simple types the View needs, e.g.
struct TitlePriceView: View {
let title: String
#Binding var price: Double
// etc.
TitlePriceView(title: product.title, price: $product.price)
I come from the UIKit universe and am struggling to understand SwiftUI's object observers.
Here is what I have:
MyEnum
enum MyEnum: String, Identifiable, CaseIterable {
static var all: [MyEnum] { MyEnum.allCases.sorted(by: { $0.value > $1.value }) }
case value1 = "value1"
case value2 = "value2"
var value: Double { UserDefaults.standard.double(forKey: rawValue) }
}
I also have a service that calls an API and updates all the MyEnum values (by setting the new value in the UserDefaults).
And the UI:
struct ContentView: View {
var body: some View {
TestHeader()
TestList()
}
}
struct TestHeader: View {
var body: some View {
Button {
APIService.reloadAll()
}, label: {
Text("Update")
}
}
}
struct TestList: View {
var body: some View {
List {
ForEach(MyEnum.all) {
MyEnumRow(myEnum: $0)
.listRowInsets(EdgeInsets())
}
}
.listStyle(PlainListStyle())
}
}
struct MyEnumRow: View {
var myEnum: MyEnum
var body: some View {
HStack(spacing: 10) {
Text(myEnum.rawValue)
Text(myEnum.value)
}
}
}
Of course when I tap the update button the myEnum list isn't updated, but I don't know how to make it responsive to content change. Could you please help me?
You can use #AppStorage to watch a particular value in UserDefaults. This is slightly more complex in your case because you don't know what key to be watching until the view is created. I opted to have the AppStorage passed in from the parent view.
struct ContentView: View {
var body: some View {
TestHeader()
TestList()
}
}
struct TestHeader: View {
var body: some View {
Button(action: {
UserDefaults.standard.set(Double(Date().timeIntervalSince1970), forKey: MyEnum.allCases.first!.rawValue)
}) {
Text("Update")
}
}
}
struct TestList: View {
var body: some View {
List {
ForEach(MyEnum.all) {
MyEnumRow(myEnum: $0, valueStorage: AppStorage(wrappedValue: 0, $0.rawValue))
.listRowInsets(EdgeInsets())
}
}
.listStyle(PlainListStyle())
}
}
struct MyEnumRow: View {
var myEnum: MyEnum
#AppStorage var valueStorage : Double
var body: some View {
HStack(spacing: 10) {
Text(myEnum.rawValue)
Text("\(valueStorage)")
}
}
}
enum MyEnum: String, Identifiable, CaseIterable {
static var all: [MyEnum] { MyEnum.allCases.sorted(by: { $0.value > $1.value }) }
case value1 = "value1"
case value2 = "value2"
var value: Double { UserDefaults.standard.double(forKey: rawValue) }
var id: String { self.rawValue }
}
I want to create a shake animation when the User presses the "save"-button and the input is not valid. My first approach is this (to simplify I removed the modifiers and not for this case relevant attributes):
View:
struct CreateDeckView: View {
#StateObject var viewModel = CreateDeckViewModel()
HStack {
TextField("Enter title", text: $viewModel.title)
.offset(x: viewModel.isValid ? 0 : 10) //
.animation(Animation.default.repeatCount(5).speed(4)) // shake animation
Button(action: {
viewModel.buttonPressed = true
viewModel.saveDeck(){
self.presentationMode.wrappedValue.dismiss()
}
}, label: {
Text("Save")
})
}
}
ViewModel:
class CreateDeckViewModel: ObservableObject{
#Published var title: String = ""
#Published var buttonPressed = false
var validTitle: Bool {
buttonPressed && !(title.trimmingCharacters(in: .whitespacesAndNewlines) == "")
}
public func saveDeck(completion: #escaping () -> ()){ ... }
}
But this solution doesn't really work. For the first time when I press the button nothing happens. After that when I change the textfield it starts to shake.
using GeometryEffect,
struct ContentView: View {
#StateObject var viewModel = CreateDeckViewModel()
var body: some View {
HStack {
TextField("Enter title", text: $viewModel.title)
.modifier(ShakeEffect(shakes: viewModel.shouldShake ? 2 : 0)) //<- here
.animation(Animation.default.repeatCount(6).speed(3))
Button(action: {
viewModel.saveDeck(){
...
}
}, label: {
Text("Save")
})
}
}
}
//here
struct ShakeEffect: GeometryEffect {
func effectValue(size: CGSize) -> ProjectionTransform {
return ProjectionTransform(CGAffineTransform(translationX: -30 * sin(position * 2 * .pi), y: 0))
}
init(shakes: Int) {
position = CGFloat(shakes)
}
var position: CGFloat
var animatableData: CGFloat {
get { position }
set { position = newValue }
}
}
class CreateDeckViewModel: ObservableObject{
#Published var title: String = ""
#Published var shouldShake = false
var validTitle: Bool {
!(title.trimmingCharacters(in: .whitespacesAndNewlines) == "")
}
public func saveDeck(completion: #escaping () -> ()){
if !validTitle {
shouldShake.toggle() //<- here (you can use PassThrough subject insteadof toggling.)
}
}
}
I have a list of items of different classes derived from the same class.
The goal: editing any object using a different view
The model:
class Paper: Hashable, Equatable {
var name: String
var length: Int
init() {
name = ""
length = 0
}
init(name: String, length: Int) {
self.name = name
self.length = length
}
static func == (lhs: Paper, rhs: Paper) -> Bool {
return lhs.length == rhs.length
}
func hash(into hasher: inout Hasher) {
hasher.combine(length)
}
}
class ScientificPaper: Paper {
var biology: Bool
override init(name: String, length: Int) {
biology = false
super.init(name: name, length: length)
}
}
class TechnicalPaper: Paper {
var electronics: Bool
override init(name: String, length: Int) {
electronics = false
super.init(name: name, length: length)
}
}
The main view containing the list.
struct TestView: View {
#Binding var papers: [Paper]
#State private var edit = false
#State private var selectedPaper = Paper()
var body: some View {
let scientificBinding = Binding<ScientificPaper>(
get: {selectedPaper as! ScientificPaper},
set: { selectedPaper = $0 }
)
VStack {
List {
ForEach(papers, id: \.self) { paper in
HStack {
Text(paper.name)
Text("\(paper.length)")
Spacer()
Button("Edit") {
selectedPaper = paper
edit = true
}
}
}
}
}
.sheet(isPresented: $edit) {
VStack {
if selectedPaper is ScientificPaper {
ScientificForm(paper: scientificBinding)
}
if selectedPaper is TechnicalPaper {
TechnicalForm(paper: technicalBinding)
}
}
}
}
}
The custom view for each class.
struct ScientificForm: View {
#Binding var paper: ScientificPaper
var body: some View {
Form {
Text("Scientific")
TextField("Name: ", text: $paper.name)
TextField("Length: ", value: $paper.length, formatter: NumberFormatter())
TextField("Biology: ", value: $paper.biology, formatter: NumberFormatter())
}
}
}
struct TechnicalForm: View {
#Binding var paper: TechnicalPaper
var body: some View {
Form {
Text("Technical")
TextField("Name: ", text: $paper.name)
TextField("Length: ", value: $paper.length, formatter: NumberFormatter())
TextField("Electronics: ", value: $paper.electronics, formatter: NumberFormatter())
}
}
}
Problem is that at run time I get the following:
Could not cast value of type 'Paper' to 'ScientificPaper'.
maybe because the selectedPaper is already initialized as Paper.
What is the right strategy to edit list items belonging to different classes?
The error is due to creating binding in body, which calculates on every refresh, so binding is invalid.
The solution is to make binding as computable property, so it is requested only after validation in correct flow.
Tested with Xcode 12.1 / iOS 14.1 (demo is for scientificBinding only for simplicity)
struct TestView: View {
#Binding var papers: [Paper]
#State private var edit = false
#State private var selectedPaper = Paper()
var scientificBinding: Binding<ScientificPaper> { // << here !!
return Binding<ScientificPaper>(
get: {selectedPaper as! ScientificPaper},
set: { selectedPaper = $0 }
)
}
var body: some View {
VStack {
List {
ForEach(papers, id: \.self) { paper in
HStack {
Text(paper.name)
Text("\(paper.length)")
Spacer()
Button("Edit") {
selectedPaper = paper
edit = true
}
}
}
}
}
.sheet(isPresented: $edit) {
VStack {
if selectedPaper is ScientificPaper {
ScientificForm(paper: scientificBinding)
}
// if selectedPaper is TechnicalPaper {
// TechnicalForm(paper: technicalBinding)
// }
}
}
}
}
I'd like to be able to create a List from an enum that conforms to CaseIterable and CustomStringConvertible e.g.
public enum HairColor: Int, Codable, CaseIterable, CustomStringConvertible {
public var description: String {
switch self {
case .black:
return "Black"
case .blond:
return "Blond"
case .brown:
return "Brown"
case .red:
return "Red"
case .grey:
return "Gray"
case .bald:
return "Bald"
}
}
case blond, brown, black, red, grey, bald
}
struct ContentView: View {
var body: some View {
SwiftUIHelpers.enumToList(HairColor)
}
}
This is the approach I've tried but I get the error: "Cannot convert value of type 'Text' to closure result type '_"
struct SwiftUIHelpers {
static func enumToList<T: CaseIterable, RandomAccessCollection>(_ a: T) -> some View {
List {
ForEach(a, id: \.rawValue) { (o: CustomStringConvertible) in
Text(o.description)
}
}
}
}
What is the error on my ways?!?
Here is working solution. Tested with Xcode 11.4 / iOS 13.4.
struct SwiftUIHelpers {
static func enumToList<T: CaseIterable>(_ t: T.Type) -> some View
where T.AllCases: RandomAccessCollection, T: Hashable & CustomStringConvertible {
List {
ForEach(t.self.allCases, id: \.self) { o in
Text(o.description)
}
}
}
}
struct ContentView: View {
var body: some View {
SwiftUIHelpers.enumToList(HairColor.self)
}
}
public enum HairColor: Int, Codable, Hashable, CaseIterable, CustomStringConvertible {
public var description: String {
switch self {
case .black:
return "Black"
case .blond:
return "Blond"
case .brown:
return "Brown"
case .red:
return "Red"
case .grey:
return "Gray"
case .bald:
return "Bald"
}
}
case blond, brown, black, red, grey, bald
}
Update: Having played with this more I've found that the following extension can be much more helpful
extension CaseIterable where Self.AllCases: RandomAccessCollection, Self: Hashable & CustomStringConvertible {
static func toForEach() -> some View {
ForEach(Self.allCases, id: \.self) { o in
Text(o.description).tag(o)
}
}
}
because gives wider reuse possibility, like
List { HairColor.toForEach() }
and this
Form { HairColor.toForEach() }
and
struct DemoHairColorPicker: View {
#State private var hairColor: HairColor = .red
var body: some View {
VStack {
Text("Selected: \(hairColor.description)")
Picker(selection: $hairColor, label: Text("Hair")) { HairColor.toForEach() }
}
}
}
and of course in any stack VStack { HairColor.toForEach() }
Not really an answer to all parts of your question - only the first part - but offered here as an alternative...
Might be worth considering the use of #EnvironmentObject for a #Published property? I used this to populate a sidebar style menu for a macOS target.
Step 1:
Use your enum. My enum is written a little differently to yours but I thought to leave it that way because it provides an alternate construction... but with the same outcome.
(Conforming to CaseIterable here allows us to use the .allCases method in Step 2.)
enum HairColor: Int, CaseIterable {
case blond = 0, brown, black, red, grey, none
var description: String {
switch self {
case .blond: return "Blond"
case .brown: return "Brown"
case .black: return "Black"
case .red: return "Red"
case .grey: return "Grey"
case .none: return "Bald"
}
}
}
Step 2:
Create a struct for your model and include a static property that maps all cases of your HairColor enum.
(Conforming to Identifiable here allows us to use the cleaner ForEach syntax in Step 4 - that is - use ForEach(appData.hairColors) in lieu of ForEach(appData.hairColors, id: \.id)).
import SwiftUI
struct Hair: Codable, Hashable, Identifiable {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
static var colors: [Hair] {
return HairColor.allCases.map({ Hair(id: $0.rawValue, name: $0.description ) })
}
}
Step 3:
Create a class that conforms to ObservableObject and that contains a #Published wrapped property to allow you to broadcast your HairColor via #EnvironmentObject.
import Combine // <- don't forget this framework import!
import SwiftUI
final class AppData: ObservableObject {
#Published var hairColors = Hair.colors
}
Step 4:
Use in a View.
struct HairList: View {
#EnvironmentObject var appData: AppData
#State var selectedHair: Hair?
var body: some View {
VStack(alignment: .leading) {
Text("Select...")
.font(.headline)
List(selection: $selectedHair) {
ForEach(appData.hairColors) { hairColor in
Text(hairColor.name).tag(hairColor)
}
}
.listStyle(SidebarListStyle())
}
.frame(minWidth: 100, maxWidth: 150)
.padding()
}
}
Step 5:
Remember to inject the environment object into the preview to make the preview usable.
struct HairList_Previews: PreviewProvider {
static var previews: some View {
HairList(selectedHair: .constant(AppData().hairColors[1]))
.environmentObject(AppData())
}
}