How to render resursively with SwiftUI? - swift

I got this long error:
Function opaque return type was inferred as '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<[Dictionary<String, JSON>.Keys.Element], Dictionary<String, JSON>.Keys.Element, TupleView<(some View, some View)>?>>>, some View>' (aka '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<Array<String>, String, Optional<TupleView<(some View, some View)>>>>>, some View>'), which defines the opaque type in terms of itself
I think the issue is the "resursive" character of the rendering as the error log itself says also:
which defines the opaque type in terms of itself
If I replace in the second if-else-statement, as in comment you see, and return only a text, then I got no error.
#ViewBuilder
func showNode(json: JSON) -> some View {
if let v = json.get() as? String {
Text("\(v)").padding()
} else if let d = json.get() as? [String: JSON] {
VStack {
ForEach(d.keys.sorted(), id: \.self) { k in
if let v = d[k] {
Text("\(k)").padding()
showNode(json: v)
}
}
}
// Text("").padding()
} else {
Text("").padding()
}
}
Method is a recursive method, which should render a json tree.
And the JSON
public enum JSON {
case string(String)
case number(Float)
case object([String:JSON])
case array([JSON])
case bool(Bool)
func get() -> Any {
switch self {
case .string(let v):
return v
case .number(let v):
return v
case .object(let v):
return v
case .array(let v):
return v
case .bool(let v):
return v
}
}
}
And all I do render it on screen:
struct ContentView: View {
var data: JSON? = nil
init() {
updateValue()
return
}
mutating func updateValue() {
do {
if let jsonURL = Bundle.main.url(forResource: "user", withExtension: "json") {
let jsonData = try Data(contentsOf: jsonURL)
guard let d2 = try JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves) as? JSON else {
print("Can not convert to d2")
return
}
data = d2
}
} catch {
}
}
var body: some View {
VStack {
if let data = self.data {
showNode(json: data)
}
}
}

some View is just a language feature that allows you to not write out the whole return type of the method.
If you actually try to write out the whole return type of the method by meticulously following all the language rules, you will find that you run into a problem. Because you need to know what the return type of showNode is, in order to know the return type of showNode! This is partly due to its recursive nature, and partly due to #ViewBuilder.
If you are wondering what on earth is _ConditionalContent, those come from buildEither, which is what your if statements translate into when put inside a #ViewBuilder. TupleView comes from buildBlock, and all their type parameters are determined by the types of the expressions you put inside, one of them being the showNode call, whose type we are in the middle of figuring out.
You can fix this by either using AnyView, the type-erased view:
func showNode(json: JSON, depth: Int = 1) -> AnyView {
if let v = json.get() as? String {
return AnyView(Text("\(v)").padding())
} else if let d = json.get() as? [String: JSON] {
return AnyView(VStack {
ForEach(d.keys.sorted(), id: \.self) { k in
if let v = d[k] {
Text("\(k)").padding()
showNode(json: v)
}
}
})
} else {
return AnyView(Text("").padding())
}
}
Or make a new View type to stop the infinite recursion:
struct NodeView: View {
let json: JSON
var body: some View {
if let v = json.get() as? String {
Text("\(v)").padding()
} else if let d = json.get() as? [String: JSON] {
VStack {
ForEach(d.keys.sorted(), id: \.self) { k in
if let v = d[k] {
Text("\(k)").padding()
NodeView(json: v)
}
}
}
} else {
Text("").padding()
}
}
}
A few additional notes:
The way that you are parsing the JSON right now is incorrect. JSONSerialization won't give you your own JSON type. You should probably use a custom Codable implementation instead, but exactly how to do that belongs to another question.
The view that this code draws doesn't actually show the "levels" of the JSON. Not sure if that is intended or not
if let v = json.get() as? String { doesn't handle Bools or Floats or arrays. If you want to handle those, you need to write checks for them as well.

Related

Function that returns an array

I'm trying to get an array to return from a function I call but the return optionArray in the below code give me a "Use of unresolved identifier optionArray.
public func getAdminSites(){
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
var optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}
There are two errors:
Function definition is missing a return type
OptionArray (stored variable) is declared in a control flow if scope and not accessible on a function body level
When you define a function, you can optionally define one or more
named, typed values that the function takes as input, known as
parameters. You can also optionally define a type of value that the
function will pass back as output when it is done, known as its return
type.
source
Fixed code:
public func getAdminSites() -> [String] {
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
var optionArray = [String]()
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}

Accessing data after calling an API

first, I'm very (very) new to Swift programming. Challenging but so interesting!
Right now, in a Playground, I'm trying to fetch the data from a JSON that I can access using a URL.
I need to store the data somewhere (in this case I need to store an array of BixiStationViewModel so I can later on play with the data I fetch from the URL.
I think the issue is coming from the asynchronous process that is fetching the data and then having my code processing it.
You can see at the end of the code the print(allBixi.allStations) statement: it returns an empty array.
import Foundation
// JSON structure
struct BixiStationDataModel: Codable {
let lastUpdated, ttl: Int?
let data: StationsData?
enum CodingKeys: String, CodingKey {
case lastUpdated = "last_updated"
case ttl, data
}
}
struct StationsData: Codable {
let stations: [StationData]?
}
struct StationData: Codable {
let stationID: String?
let numBikesAvailable, numEbikesAvailable, numBikesDisabled, numDocksAvailable: Int?
let numDocksDisabled, isInstalled, isRenting, isReturning: Int?
let lastReported: Int?
let eightdHasAvailableKeys: Bool?
let eightdActiveStationServices: [EightdActiveStationService]?
enum CodingKeys: String, CodingKey {
case stationID = "station_id"
case numBikesAvailable = "num_bikes_available"
case numEbikesAvailable = "num_ebikes_available"
case numBikesDisabled = "num_bikes_disabled"
case numDocksAvailable = "num_docks_available"
case numDocksDisabled = "num_docks_disabled"
case isInstalled = "is_installed"
case isRenting = "is_renting"
case isReturning = "is_returning"
case lastReported = "last_reported"
case eightdHasAvailableKeys = "eightd_has_available_keys"
case eightdActiveStationServices = "eightd_active_station_services"
}
}
struct EightdActiveStationService: Codable {
let id: String?
}
// Calling the API
class WebserviceBixiStationData {
func loadBixiStationDataModel(url: URL, completion: #escaping ([StationData]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiStationDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data?.stations)
}
}
}.resume()
}
}
// Data Model
class BixiStationViewModel {
let id = UUID()
let station: StationData
init(station: StationData) {
self.station = station
}
var stationID: String {
return self.station.stationID ?? ""
}
var numBikesAvailable: Int {
return self.station.numBikesAvailable ?? 0
}
var numDocksAvailable: Int {
return self.station.numDocksAvailable ?? 0
}
var isInstalled: Int {
return self.station.isInstalled ?? 0
}
var isReturning: Int {
return self.station.isReturning ?? 0
}
}
class BixiStationListModel {
init() { fetchBixiApiDataModel() }
var allStations = [BixiStationViewModel]()
private func fetchBixiApiDataModel() {
guard let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") else {
fatalError("URL is not correct")
}
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
}
}
}
}
// Checking if the data has been dowloaded
let allBixi = BixiStationListModel()
print(allBixi.allStations)
How can I fix the code so I could access the values in the var allStations = [BixiStationViewModel]()
Thanks in advance, I've Benn working on it this issue for a while now and this would help me a lot in my app development
In a playground you need continuous execution to work with a url response.
Add PlaygroundPage.current.needsIndefiniteExecution = true to the top of your file (doesn't matter where it's added but I always do the top)
To get your data to print, add a print statement inside your loadBixiStationDataModel callback
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
print(stations)
}
}

enum method returning a dynamic type

I have an enum and I'd like to create a method to return a different type for every case.
For example, I have a dictionary [String: Any]. To process the values I'm using the enum to create an array of keys:
enum Foo {
case option1
case option2
func createKey() -> [String] {
switch self {
case .option1: return ["scenario1"]
case .option2: return ["scenario2"]
}
}
}
Once I have the values, I need to cast them to a the proper type to be able to use them. Right now I'm doing it manually using if-statements but it would reduce a lot of code if I can somehow create a method in the enum to return the proper type. My current code:
let origin: [String: Any] = ["scenario2": "someText"]
let option: Foo = .option2
option.createKey().forEach {
guard let rawValue = origin[$0] else { return }
switch option {
case .option1:
guard let value = rawValue as? Int else { return }
print("Value is an Int:", value)
case .option2:
guard let value = rawValue as? String else { return }
print("Value is a String:", value)
}
}
What I would like to achieve is something like:
option.createKey().forEach {
guard let rawValue = origin[$0] as? option.getType() else { return }
}
Is this possible?
I think the core of the problem here is that Swift has strict typing. That means types must be known at compile time. This, obviously, is legal:
let s : Any = "howdy"
if let ss = s as? String {
print(ss)
}
But this is not legal:
let s : Any = "howdy"
let someType = String.self
if let ss = s as? someType { // *
print(ss)
}
someType must be a type; it cannot be a variable hiding a type inside itself. But that is precisely what, in effect, you are asking to do.

How to produce a nil of a generic type

I would like to declare a generic class which holds/obtains a variable of type Any?, but casts this variable to a given type, when requested. Something like this
class A<T> {
var o: NSObject!
var k: String!
var v: T {
get { return o.value(forKeyPath: k) as! T }
set { o.setValue(newValue, forKeyPath: k) }
}
}
I would like to this to work so that when o.value(forKeyPath: k) is nil and T can hold a nil (it is ExpressibleByNilLiteral), v returns nil (of type T). As it is, it crashes because of the as!. Is it possible to do this?
An attempted crack at this looks like this (suggested by
How to make a computed property of a generic class depend on the class constraints)
class A<T> {
var o: NSObject!
var k: String!
var v: T {
get {
let x = o.value(forKeyPath: k)
if let E = T.self as? ExpressibleByNilLiteral.Type {
if let x = x { return x as! T }
let n: T = <nil of type T> <---- not sure what to put here
return n
} else {
return x as! T
}
}
set { o.setValue(newValue, forKeyPath: k) }
}
}
but I am not sure how to make it work.
Not sure why, but this actually works
func cast<T>(_ v: Any?)->T {
if let E = T.self as? ExpressibleByNilLiteral.Type {
if let v = v {
return v as! T
} else {
return E.init(nilLiteral: ()) as! T
}
} else {
return v as! T
}
}
I thought I already tried this and it did not work... Anyway, now we can substitute all as! calls with cast when we do not want to crash on nil, if the type we are casting to can take it, e.g. in the getter of my question.

Casting to generic optional in Swift

I'm fiddling around with generics in Swift and hit something I can't figure out: If I cast a value into the type of a generic parameter, the cast is not performed. If I try the same with static types, it works.
class SomeClass<T> {
init?() {
if let _ = 4 as? T {
println("should work")
} else {
return nil
}
}
}
if let _ = SomeClass<Int?>() {
println("not called")
}
if let _ = 4 as? Int? {
println("works")
}
Can anybody explain this behavior? Shouldn't be both cases equivalent?
Update
The above example is simplified to the max. The following example illustrates the need for a cast a little better
class SomeClass<T> {
init?(v: [String: AnyObject]) {
if let _ = v["k"] as? T? {
print("should work")
} else {
print("does not")
return nil
}
}
}
if let _ = SomeClass<Int?>(v: ["k": 4]) {
print("not called")
}
if let _ = SomeClass<Int>(v: ["k": 4]) {
print("called")
}
2nd Update
After #matt made me learn about AnyObject and Any and #Darko pointed out in his comments how dictionaries make my example too complicated, here's my next refinement
class SomeClass<T> {
private var value: T!
init?<U>(param: U) {
if let casted = param as? T {
value = casted
} else {
return nil
}
}
}
if let _ = SomeClass<Int?>(param: Int(4)) {
println("not called")
}
if let _ = SomeClass<Int>(param: Int(4)) {
println("called")
}
if let _ = Int(4) as? Int? {
println("works")
}
if let _ = (Int(4) as Any) as? Int? {
println("Cannot downcast from Any to a more optional type 'Int?'")
}
I tried using init?(param: Any) before, but that yields the same problem illustrated in the last if which is discussed elsewhere.
So all it comes down to: Has anyone really been far as to ever cast anything to a generic optional type? In any case? I'm happy to accept any working example.
This is really not about generics at all; it's about AnyObject (and how casting works). Consider:
let d = ["k":1]
let ok = d["k"] is Int?
print (ok) // true
// but:
let d2 = d as [String:AnyObject]
let ok2 = d2["k"] is Int?
print (ok2) // false, though "is Int" succeeds
Since your initializer casts the dictionary up to [String:AnyObject] you are in this same boat.
This works as expected now - in Playgrounds as well as in projects. Retested in Swift 5.4:
class SomeClass<T> {
private var value: T
init?<U>(param: U) {
if let casted = param as? T {
value = casted
} else {
return nil
}
}
}
if let _ = SomeClass<Int?>(param: Int(4)) {
print("called")
}
if let _ = SomeClass<Int>(param: Int(4)) {
print("called")
}
which prints called two times as expected.
As far I can see the goal of your updated code is to find out if the passed parameter (the value of the dict) is of type T. So you are mis-using the as? cast to check the type. What you actually want is the "is" operator.
class SomeClass<T> {
init?(v: [String: AnyObject]) {
if v["k"] is T {
print("called if AnyObject is of type T")
} else {
print("called if AnyObject is not of type T")
return nil
}
}
}
if let _ = SomeClass<Int>(v: ["k": 4]) {
print("called")
}
if let _ = SomeClass<Int?>(v: ["k": 4]) {
print("not called")
}