Swift - How to declare a private nested struct? - swift

I want o make my code more readable, so I decided to make some repeated dictionary keys, soft coded... So I created a new .swift file, with 2 structs in it:
struct DatabaseTableNames {
let Photo = PhotoTable()
}
private struct PhotoTable {
let lowQuality = "lowQuality"
let highQuality = "highQuality"
let numberOfLikes = "numberOfLikes"
}
So I have the initial struct I'm gonna use and the second one, which I don't want it to be visible outside of the file's scope... The thing is, it says that the Photo property of the DatabaseTableNames struct, needs to be declared as fileprivate since PhotoTable is private...
What am I doing wrong here?

The key was to nest PhotoTable and make its properties static.
struct DatabaseTableNames {
struct PhotoTable {
static let lowQuality = "lowQuality"
static let highQuality = "highQuality"
static let numberOfLikes = "numberOfLikes"
}
}
Example Use:
let test = DatabaseTableNames.PhotoTable.lowQuality
print(test)

swap around your private settings - and make sure this is defined in the same file as the UIViewController you want to use it
private struct DatabaseTableNames {
let Photo = PhotoTable()
}
struct PhotoTable {
let lowQuality = "lowQuality"
let highQuality = "highQuality"
let numberOfLikes = "numberOfLikes"
}
and then, access the struct
private var photo : DatabaseTableNames?

Related

Wait for request to be precessed for displaying information [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I want to make a Swift (5.4.6) app to display information about cryptos. For this I need to call a server to get the information I want to display. However my application don't wait for the data to be processed so I get an error for wanting to display a nil.
Here is my code for the call to the server :
var cryptos = Cryptos(cryptos: nil)
let url = URL(string: urlString)
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) {(data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
cryptos = try decoder.decode(Cryptos.self, from: data!)
print(cryptos.cryptos![6].name)
}
catch {
print("Impossible de convertir les données reçues")
}
}
}
dataTask.resume()
return cryptos
}
Here are the struct I want to decode the json into :
struct Crypto: Codable {
let name: String
let symbol: String
let price: Float
let totalSupply: Float
let marketCap: Float
let change24h: Float
}
struct Cryptos: Codable {
let cryptos: [Crypto]?
}
Finally here is the code in my view :
var lesCryptos = Cryptos()
struct CryptoList: View {
var body: some View {
HStack {
NavigationView {
}
}
.onAppear(perform: getAllCryptosInfos)
}
}
func getAllCryptosInfos() {
lesCryptos = Worker().getAllCryptosInfos()
print(lesCryptos.cryptos![7].name)
}
The error appears when I want to print the name ("lesCryptos.cryptos!" is nil)
Also here is a sample of the JSON i get :
{
"cryptos": [
{
"name":"Bitcoin",
"symbol":"BTC",
"price":36301.41347043701,
"totalSupply":18728856,
"marketCap":679883945484.275,
"change24h":0.36443243
},
{
"name":"Ethereum",
"symbol":"ETH",
"price":2784.5450982190573,
"totalSupply":116189845.499,"marketCap":323535864747.07007,
"change24h":3.46116544
}
]
}
First, convert your struct to a class and setup default values so that there is no nil values to cause crashes.
class Crypto: Codable {
let name: String
let symbol: String
let price: Float
let totalSupply: Float
let marketCap: Float
let change24h: Float
init() {
name = ""
symbol = ""
price = 0.0
totalSupply = 0.0
marketCap = 0.0
change24h: 0.0
}
}
In my example, notice that the init() constructor takes in no values. This allows me to create an object Crypto() without passing any values. This means that my object will have those default values, which is useful in your case because you're crashing from nil values.
struct ExampleView: View {
#State var someCrypto = Crypto()
var body: some View {
Text("\(someCrypto.name)"
.onAppear(
someCrypto.name = "Example"
)
}
}
In this example I'm using my default constructor for my Crypto object. That ensures that my view can display the name "" given by someCrypto.name. Then I use onAppear to simulate fetching of your data from the API. You will notice that the view updates automatically. This is because of the #State object which essentially tells the view to listen for any changes, and those changes are two-way. While in this context a Text() is not actually going to use it two-way a TextField() would.
Additional reading and studying should be looked up for #State #ObservedObject #ObservableObject and #Published which will help you get off your feet and use SwiftUI much more responsively.
Thank you for all your responses, however I managed to resolve my problem using completionHandler. Here is the link to the question and answers that helped me : stackoverflow question

How to pass uiviewcontroller data to swiftui widget class

How to pass uiviewcontroller data to swiftui widget class i am unable to do it with app groups also write now i am sending it with userdefaults but unable to do it
if let userDefaults = UserDefaults(suiteName: "group.com.soup.ios.app") {
UserDefaults.standard.set(self.dat ?? "", forKey: "date")
UserDefaults.standard.set(self.textData.text ?? "", forKey: "text")
}
if let userDefaults = UserDefaults(suiteName: "group.com.soup.ios.app") {
let text = UserDefaults.standard.object(forKey: "text") as? String
Text(text ?? "Add Data").lineLimit(2)
.truncationMode(.middle)
}
I managed it to do it like this and it worked
extension UserDefaults {
static let group = UserDefaults(suiteName: "group.com.your.domain")!
}
setting data:
UserDefaults.group.set("objectToShare", forKey: "keyForTheObject")
getting data:
let object = UserDefaults.group.object(forKey: "keyForTheObject")
This solution is in swift 5.
In order to pass data between targets, you need to:
create your sharedDefaults structure
declare your global data variable
change the variable's data from your target\viewcontroller using support variables.
First of all create a new .swift file. Go on inspector>file>target membership and select the targets you want to comunicate.
SharedDefaults helper
//This goes in your new swift file
let sharedUserdefaults = UserDefaults(suiteName: SharedDefault.suitName)
struct SharedDefault {
static let suitName = "group.com.soup.ios.app"
struct Keys{
static let Key = "text"
}
}
Global data variable
//This goes in your new swift file
//This is an array of strings change the type by changing what's after "data:"
var data: [String] {
get {
return sharedUserdefaults?.stringArray(forKey: Key) as? [String] ?? [String]()
} set {
}
}
So what's above is a get/set variable which stores and receives the data you save/get from your widget and your parent app.
Actually saving/retrieving stuff
Now to use this stuff either in your app or target, you have to
Declare the support variables:
var myData: [String] = [] //This is not required but if you need to display contents in real time I'd suggest you use this.
var currentData = data //Helper variable to pass data to the sharedVariable
override func viewDidLoad() {
super.viewDidLoad()
myData.append(contentsOf: data)
}
Save stuff:
//Update both data and current data
myData.append("ok")
currentData.append("ok")
sharedUserdefaults?.set(currentData, forKey: SharedDefault.Keys.Key)
Delete stuff:
//Update both data and current data
mydata.remove(at: 0)
currentData.remove(at: 0)
sharedUserdefaults?.set(currentData, forKey: SharedDefault.Keys.Key)
That's it!

Swift : retrieving struct values in swiftui view is not working

I am new to swift and need to define some form of Global Dictionary that I can access the content throughout my project. My understanding is that struct class can be used for that so
I created a struct and appended values to it, now I want to access each of those values in view
this is my product struct
struct Product {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}
then created a global
import Foundation
struct Global {
static var productList = [Product]()
}
this is how I append many products to Product
class SearchResult : ObservableObject {
var productList = [Product]()
//There could be hundreds of product in the array
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
this is my search result view where I want to display the content of the Product
struct SearchResultView: View {
var searchResults = Global.productList
var body: some View {
VStack {
List {
ForEach(model.searchResults, id: \.self) { text in
Text(text)
}
}
}
}
}
I can seem to get it to show in the searchResultView. What am doing wrong ?
I keep getting this error
Generic struct 'ForEach' requires that 'Product' conform to 'Hashable'
Initializer 'init(_:)' requires that 'Product' conform to 'StringProtocol'
You need to set "searchResults" equal to your "productList"
Right now your searchResults is EMPTY. It simply exists as an instance of your struct with no data in it.
one option is to make the variable scope global and then set your new variable = to it
self.searchResults = Global.productList
--EDIT
You are close.
Where you set your var here
var searchResults = Global.productList
it needs to be like this.
var searchResults = [Product]() // ->Creates an instance of the struct object
Then set it equal to your global array.
self.searchResults = Global.productList
ALSO you should remove you redundant variable var productList = [Product]()
Furthermore, some things to note
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
you are doing extra work by using all the let variables.
A better way is doing it like so.
for product in productArray {
Global.productList.append(Product(name: product.name, aisleNo: product.aisleNo, location_section: product.location_section, location_zone: product.location_zone, productPrice: Int(product.productPrice)))
}
EDIT - Hashable Erorr
try this out
struct Product: Hashable {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}

Two phase initialization deadlock: how to init class's properties before super.init() if you have to call self.someMethod() to init them?

Confused with Swift's two-phase initialization.
you should init all properties before super.init() call
to initialize it you have to call some private method (this method is needed for reuse purposes and you don't want to bloat init() method also)
to call your private method you have call super.init() first. Deadlock.
Here is example code that won't compile:
class A:NSObject {
private var url1:URL
private var url2:URL
let settings:Dictionary<String, String>
init(setting:Dictionary<String, String>) {
self.configureAddresses()
super.init()
}
private func configureAddresses() {
/* ...some long and difficult reusable somewhere in this class configuration code... */
self.url1 = URL(string:"result")!
self.url2 = URL(string:"result")!
}
}
p.s. i know i can assign stub values before super.init() call and then reconfigure them, but it will be too ugly if i have properties if non-trivial types like URL or my custom classes.
Option 1:
The given code suggests that the URLs are predefined with constants, so declare them directly
class A:NSObject {
private var url1 = URL(string:"result")!
private var url2 = URL(string:"result")!
Option 2:
The literal strings depend on another value for example in the dictionary then declare the variables lazily and as optional.
class A:NSObject {
private lazy var url1 : URL? = {
guard let result = settings["result"] as? String else { return nil }
return URL(string: result)
}()
In both options exchange the order in init
init(setting:Dictionary<String, String>) {
super.init()
self.configureAddresses()
}
Another option, make your configureAddresses() a class method:
class A: NSObject {
private var url1: URL
private var url2: URL
let settings: [String: String]
init(setting: [String: String]) {
self.settings = setting
(self.url1, self.url2) = A.configureAddresses()
super.init()
}
private static func configureAddresses() -> (URL, URL) {
var result1: String = ""
var result2: String = ""
/* ...some long and difficult reusable somewhere in this class configuration code... */
return (URL(string: result1)!, URL(string: result2)!)
}
}
By the way, you should better know that Always avoid Optionals is far worse than using Optionals properly.

Initialize a struct with empty variables and make it globally available

What is the best solution for initializing a struct in swift with empty variables and make it accessible over more viewcontrollers? I know how to pass data between two viewcontrollers like:
let myVC = storyboard?.instantiateViewControllerWithIdentifier("SecondVC") as! SecondVC
myVC.stringPassed = myLabel.text!
But is there another elegant solution to make it accessible overall views.
One way is to create a struct with a static variable.
struct CommonValues {
static var text: String = ""
}
Create swift file and add following code
You should create sharedInstance so it won't initialise everytime
public class AppGlobalManager {
// MARK: - Init
private init() {}
// MARK: - Var
static let sharedManager = AppGlobalManager()
var name:String = ""
}
Now you can access it anywhere with just
AppGlobalManager.sharedManager.name
NOTE: It is not good practice to use this for passing data from viewcontroler to view controller, You can use this for some global level objects like you can keep login user info here. and clear it when logout
Hope it is helpful