Getting items from [Any?] in a ForEach loop to display text - swift

Below is some code I have been trying
import SwiftUI
struct AnyOptional: View {
private var optionalArray: [Any?] = [1, 2, 3]
var body: some View {
VStack {
ForEach(optionalArray) { i in
Text("\(i)")
}
}
}
}
extension Optional: Identifiable {
public var id: String { self as! String }
}
struct AnyOptional_Previews: PreviewProvider {
static var previews: some View {
AnyOptional()
}
}
I had a similar problem with [String] which I solved by using this extension
extension String: Identifiable {
public var id: String { self }
}
but now I get an error saying Any? must inherit from NSObject.
Is there an easier way to do this?

A possible solution is to use your already created id extension:
var body: some View {
VStack {
ForEach(optionalArray) { i in
Text(i.id)
}
}
}
Note that not all objects can be casted down to String (self as! String will fail if the object can't be cast to String).
A better way is to use String(describing:).
For this you can create another extension (updated to remove the word Optional if there's some value):
extension Optional {
public var asString: String {
if let value = self {
return .init(describing: value)
}
return .init(describing: self)
}
}
and use it in the ForEach loop:
var body: some View {
VStack {
ForEach(optionalArray, id: \.asString) { i in
Text(i.asString)
}
}
}

Related

Using a protocol array with ForEach and bindable syntax

I've got an #Published protocol array that I am looping through with a ForEach to present the elements in some view. I'd like to be able to use SwiftUI bindable syntax with the ForEach to generate a binding for me so I can mutate each element and have it reflected in the original array.
This seems to work for the properties that are implemented in the protocol, but I am unsure how I would go about accessing properties that are unique to the protocol's conforming type. In the example code below, that would be the Animal's owner property or the Human's age property. I figured some sort of type casting might be necessary, but can't figure out how to retain the reference to the underlying array via the binding.
Let me know if you need more detail.
import SwiftUI
protocol Testable {
var id: UUID { get }
var name: String { get set }
}
struct Human: Testable {
let id: UUID
var name: String
var age: Int
}
struct Animal: Testable {
let id: UUID
var name: String
var owner: String
}
class ContentViewModel: ObservableObject {
#Published var animalsAndHumans: [Testable] = []
}
struct ContentView: View {
#StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
ForEach($vm.animalsAndHumans, id: \AnyTestable.id) { $object in
TextField("textfield", text: $object.name)
// if the object is an Animal, how can I get it's owner?
}
Button("Add animal") {
vm.animalsAndHumans.append(Animal(id: UUID(), name: "Mick", owner: "harry"))
}
Button("Add Human") {
vm.animalsAndHumans.append(Human(id: UUID(), name: "Ash", age: 26))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is a thorny problem with your data types.
If you can change your data types, you can make this easier to solve.
For example, maybe you can model your data like this instead, using an enum instead of a protocol to represent the variants:
struct Testable {
let id: UUID
var name: String
var variant: Variant
enum Variant {
case animal(Animal)
case human(Human)
}
struct Animal {
var owner: String
}
struct Human {
var age: Int
}
}
It will also help to add accessors for the two variants' associated data:
extension Testable {
var animal: Animal? {
get {
guard case .animal(let animal) = variant else { return nil }
return animal
}
set {
guard let newValue = newValue, case .animal(_) = variant else { return }
variant = .animal(newValue)
}
}
var human: Human? {
get {
guard case .human(let human) = variant else { return nil }
return human
}
set {
guard let newValue = newValue, case .human(_) = variant else { return }
variant = .human(newValue)
}
}
}
Then you can write your view like this:
class ContentViewModel: ObservableObject {
#Published var testables: [Testable] = []
}
struct ContentView: View {
#StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
List {
ForEach($vm.testables, id: \.id) { $testable in
VStack {
TextField("Name", text: $testable.name)
if let human = Binding($testable.human) {
Stepper("Age: \(human.wrappedValue.age)", value: human.age)
}
else if let animal = Binding($testable.animal) {
HStack {
Text("Owner: ")
TextField("Owner", text: animal.owner)
}
}
}
}
}
HStack {
Button("Add animal") {
vm.testables.append(Testable(
id: UUID(),
name: "Mick",
variant: .animal(.init(owner: "harry"))
))
}
Button("Add Human") {
vm.testables.append(Testable(
id: UUID(),
name: "Ash",
variant: .human(.init(age: 26))
))
}
}
}
}
}
Simple way to solve is extending your Testable protocol. Something likes
protocol Testable {
var id: UUID { get }
var name: String { get set }
var extra: String? { get }
}
struct Human: Testable {
let id: UUID
var name: String
var age: Int
var extra: String? { nil }
}
struct Animal: Testable {
let id: UUID
var name: String
var owner: String
var extra: String? { return owner }
}
Your ForEach block doesn't need to know the concrete type: Animal or Human, just check the extra of Testable to decide to add new element or not.

I want to create a loop that creates 10 different strings and use that strings to show 10 different cards

import Foundation
class Card: Identifiable, ObservableObject {
var id = UUID()
var result: String = ""
init() {
for _ in 1...10 {
let suit = ["D","K","B","S"][Int(arc4random() % 4]
let numStr = ["1","2","3","4"][Int(arc4random() % 4]
result = suit + numStr
}
}
}
import SwiftUI
struct EasyModeView: View {
#ObservedObject var cardModel: Card
var body : some View {
HStack {
ForEach((1...10, id: \.self) { _ in
Button { } label: { Image(cardModel.result) } } }
}
}
I created loop but it always shows me 10 same cards and i want all 10 different.
My Images in Assets are named same as combination of two variables example "D1","D2","S1","S2" etc.
Here is a right way of what you are trying:
struct ContentView: View {
var body: some View {
EasyModeView()
}
}
struct EasyModeView: View {
#StateObject var cardModel: Card = Card()
var body : some View {
HStack {
ForEach(cardModel.results) { card in
Button(action: {}, label: {
Text(card.result)
.padding(3.0)
.background(Color.yellow.cornerRadius(3.0))
})
}
}
Button("re-set") { cardModel.reset() }.padding()
}
}
struct CardType: Identifiable {
let id: UUID = UUID()
var result: String
}
class Card: ObservableObject {
#Published var results: [CardType] = [CardType]()
private let suit: [String] = ["D","K","B","S"]
init() { initializing() }
private func initializing() {
if (results.count > 0) { results.removeAll() }
for _ in 0...9 { results.append(CardType(result: suit.randomElement()! + String(describing: Int.random(in: 1...4)))) }
}
func reset() { initializing() }
}
Result:
You are using only one card, instead of creating 10. Fix like this
struct EasyModeView: View {
var body : some View {
HStack {
ForEach(0..<10) { _ in
Button { } label: {
Image(Card().result)
}
}
}
}
}

SwiftUI: extension on array to get a Binding on an instance of the array

I'd like to have an extension on Array<T> that basically returns a Binding<T>.
The purpose of this would be to have a convenient way to create a Binding in DetailView that was called from a NavigationLink of a List.
Here's what I got so far:
extension Array where Element: Identifiable {
mutating func getBinding(of instance: Element) -> Binding<Element> {
if let index = self.firstIndex(where: { $0.id == instance.id }) {
return Binding(
get: { self[index] }, //error
set: { self[index] = $0}) //error
} else {
fatalError() //implement error handling here
}
}
}
I am getting the error Escaping closure captures mutating 'self' parameter at the specified places. How can I work around this?
TL;DR
Here's how I'd like to use this extension:
class ViewModel: ObservableObject {
#Published var items: [Item]
init(with items: [Item] = [Item]()) {
self.items = items
}
}
struct Item: Identifiable, Hashable {
let id: UUID
var title: String
static var sampleItems: [Item] {
var items = [Item]()
for i in 0..<10 {
items.append(Item(id: UUID(), title: "item \(i)"))
}
return items
}
}
struct ContentView: View {
#StateObject private var viewModel = ViewModel(with: Item.sampleItems)
var body: some View {
NavigationView {
List {
Section {
ForEach(viewModel.items) { item in
//MARK: Using Array.getBinding(of:) here
NavigationLink(item.title, destination: DetailView(item: viewModel.items.getBinding(of: item)))
}
}
}
}
}
}
struct DetailView: View {
#Binding var item: Item
var body: some View {
TextField("Item Title", text: $item.title)
}
}
Change the extension to:
extension Binding {
func getBinding<T>(of instance: T) -> Binding<T>
where Value == [T], T: Identifiable {
if let index = self.wrappedValue.firstIndex(where: { $0.id == instance.id }) {
return .init(
get: { self.wrappedValue[index] }, //error
set: { self.wrappedValue[index] = $0 }) //error
} else {
fatalError() //implement error handling here
}
}
}
now instead of DetailView(item: viewModel.items.getBinding(of: item))
do DetailView(item: $viewModel.items.getBinding(of: item)) (note the $)
EDIT, Bonus:
I've got a bonus for you, hopefully you'll like it. This will make the process much nicer and the code much cleaner. Note that it has 0 difference with your current code, performance-wise.
Add this extension to begin:
extension Binding {
subscript<T>(_ index: Int) -> Binding<T> where Value == [T] {
.init(get: {
self.wrappedValue[index]
},
set: {
self.wrappedValue[index] = $0
})
}
}
and change your ForEach to this:
ForEach(viewModel.items.indices) { index in
let item = viewModel.items[index]
NavigationLink(item.title, destination: DetailView(item: $viewModel.items[index]))
}
hopefully you like it :)

I need to refresh SwiftUI View, when #propertyWrapper is passed into

I have an example struct:
public struct Axis: Hashable, CustomStringConvertible {
public var name: String
public var description: String {
return "Axis: \"\(name)\""
}
}
And property wrapper to make some operations on [Axis] struct.
#propertyWrapper
struct WrappedAxes {
var wrappedValue: [Axis] {
// This is just example, in real world it's much more complicated.
didSet {
for index in wrappedValue.indices {
var elems = Array(wrappedValue[index].name.split(separator: " "))
if elems.count>1 {
elems.removeLast()
}
let new = elems.reduce(into:"", {$0 += "\($1) "})
wrappedValue[index].name = new+("\(Date())")
} } } }
And I try to add, insert and remove Axes in SwiftUI View:
public struct ContentView: View {
#Binding var axes: [Axis]
public var body: some View {
VStack {
ForEach(axes.indices, id:\.self) {index in
HStack {
TextField("", text: $axes[index].name)
Button("Delete", action: {deleteAxis(index)})
Button("Insert", action: {insertAxis(index)})
}
}
Button("Add", action: addAxis)
}
}
var addAxis: () -> Void {
return {
axes.append(Axis(name: "New"))
print (axes)
}
}
var deleteAxis: (_:Int)->Void {
return {
if $0 < axes.count {
axes.remove(at: $0)
}
print (axes)
}
}
var insertAxis: (_:Int)->Void {
return {
if $0 < axes.count {
axes.insert(Axis(name: "Inserted"), at: $0)
}
print (axes)
}
}
public init (axes: Binding<[Axis]>) {
self._axes = axes
}
}
As far, as print (axes) shows changes are made, View never updates. I made very small App to test in which I call ContentView:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
#WrappedAxes var axes = [Axis(name: "FirstOne")]
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView(
axes: Binding (
get: {self.axes},
set: { [self] in axes = $0}))
.... // No fancy stuff
I'm open for all critique of code itself, and help: how to push this view (and all possible future subviews) to update when axes changed?
The thing is that #Binding is for nested Views. When you want to make changes in a SwiftUI view, you have to use the #State instead in the one where the action starts. Plus, you don't need to set an initialiser this way. You can set your value like this, inside an ObservableObject to handle your logic:
struct Axis {
var name: String
var description: String {
return "Axis: \"\(name)\""
}
}
final class AxisViewModel: ObservableObject {
#Published var axes: [Axis] = [Axis(name: "First")]
init() { handleAxes() }
func addAxis() {
axes.append(Axis(name: "New"))
handleAxes()
}
func insertAxis(at index: Int) {
axes.insert(Axis(name: "Inserted"), at: index)
handleAxes()
}
func handleAxes() {
for index in axes.indices {
var elems = Array(axes[index].name.split(separator: " "))
if elems.count > 1 {
elems.removeLast()
}
let new = elems.reduce(into:"", { $0 += "\($1) " })
axes[index].name = new + ("\(Date())")
}
}
}
struct ContentView: View {
#ObservedObject var viewModel = AxisViewModel()
var body: some View {
VStack {
ForEach(viewModel.axes.indices, id:\.self) { index in
HStack {
TextField("", text: $viewModel.axes[index].name)
Button("Insert", action: { viewModel.insertAxis(at: index) })
}
}
Button("Add", action: viewModel.addAxis)
}
}
}
I made a new struct Axes:
public struct Axes {
#WrappedAxes var _axes: [Axis]
public subscript (index: Int) -> Axis {
get { return _axes[index]}
set { _axes[index] = newValue}
}
// and all needed functions and vars to simulate Array behaviour:
public mutating func append(_ newElement: Axis ) {
_axes.append(newElement)
}
public mutating func insert(_ newElement: Axis, at index: Int ) {
_axes.insert(newElement, at: index)
}
public mutating func remove(at index: Int ) {
_axes.remove(at: index)
}
....
}
and put it into #ObservalbeObject:
public class Globals: ObservableObject {
#Published public var axes: Axes
....
}
Then defined AxesView as
public struct AxesView: View {
#ObservedObject var globals: Globals
public var body: some View {
...
}
...
}
That's all. For a while it works.

What is the correct way to create SwiftUI Binding with array of associative enum?

I got some (unexplained) crashes earlier today, and simplified my code to what is seen below. The crashing went away, but I am not 100% sure. Is the code below the correct way to create Binding on an array of enums? And if yes, can this code be made simpler?
import SwiftUI
enum TheEnum: Hashable {
case one(Int), two(Float)
}
class TestModel : ObservableObject {
#Published var enumArray = [TheEnum.one(5), TheEnum.two(6.0)]
}
struct ContentView: View {
#ObservedObject var testModel = TestModel()
var body: some View {
HStack {
ForEach(testModel.enumArray, id: \.self) { value -> AnyView in
switch value {
case .one(var intVal):
let b = Binding(get: {
intVal
}) {
intVal = $0
}
return AnyView(IntView(intVal: b))
case .two(var floatVal):
let b = Binding(get: {
floatVal
}) {
floatVal = $0
}
return AnyView(FloatView(floatVal: b))
}
}
}
}
}
struct IntView: View {
#Binding var intVal: Int
var body: some View {
Text("\(intVal)")
}
}
struct FloatView: View {
#Binding var floatVal: Float
var body: some View {
Text("\(floatVal)")
}
}