Web3Swift Interaction with custom ABI - swift

I am trying to interact with a smart contract I set up.
Basically the goal is to set from an iOS App 5 Parameters
projectTitle
projectLocation
projectStart
projectEnd
teamType
I want the user to set those parameters and write it on the ropsten testnetwork.
I also would like to get the contract information at a later point whenever the user feels for it.
my solidity code is working properly in remix and the contract is already deployed:
pragma solidity >=0.4.22 <0.7.0;
contract ProjectContent {
string public projectTitle;
string public projectLocation;
string public projectStart;
string public projectEnd;
string public teamType;
function projectContent(string initialProjectTitle, string initialProjectLocation, string initialProjectStart, string initialProjectEnd, string initialTeamType) public {
projectTitle = initialProjectTitle;
projectLocation = initialProjectLocation;
projectStart = initialProjectStart;
projectEnd = initialProjectEnd;
teamType = initialTeamType;
}
function setContract(string newProjectTitle, string newProjectLocation, string newProjectStart, string newProjectEnd, string newTeamType) public {
projectTitle = newProjectTitle;
projectLocation = newProjectLocation;
projectStart = newProjectStart;
projectEnd = newProjectEnd;
teamType = newTeamType;
}
function getProjectTitle() public view returns (string) {
return projectTitle;
}
function getProjectLocation() public view returns (string) {
return projectLocation;
}
function getProjectStart() public view returns (string) {
return projectStart;
}
function getProjectEnd() public view returns (string) {
return projectEnd;
}
function getTeamType() public view returns (string) {
return teamType;
}
}
My problem now is that I cannot figure out how to retrieve the data from the blockchain using the web3swift library. I am doing it like so now:
class ProjectContractViewController: UIViewController, HalfModalPresentable {
#IBOutlet weak var contractABIView: UITextView!
var halfModalTransitioningDelegate: HalfModalTransitioningDelegate?
var contractABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"initialProjectTitle\",\"type\":\"string\"},{\"name\":\"initialProjectLocation\",\"type\":\"string\"},{\"name\":\"initialProjectStart\",\"type\":\"string\"},{\"name\":\"initialProjectEnd\",\"type\":\"string\"},{\"name\":\"initialTeamType\",\"type\":\"string\"}],\"name\":\"projectContent\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newProjectTitle\",\"type\":\"string\"},{\"name\":\"newProjectLocation\",\"type\":\"string\"},{\"name\":\"newProjectStart\",\"type\":\"string\"},{\"name\":\"newProjectEnd\",\"type\":\"string\"},{\"name\":\"newTeamType\",\"type\":\"string\"}],\"name\":\"setContract\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectEnd\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectLocation\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectStart\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getProjectTitle\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getTeamType\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectEnd\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectLocation\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectStart\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"projectTitle\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"teamType\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]"
let str = "0x6080604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806323a35e62146100bf57806337a4fc7d1461014f5780634a5736fd146101df5780634b04811e1461026f5780634e9d1281146102ff57806363afee221461038f578063775e6d451461051057806393ee0402146105a0578063c3e20c9f14610630578063d8045412146106c0578063dad375ff14610750578063f020cd19146108d1575b600080fd5b3480156100cb57600080fd5b506100d4610961565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101145780820151818401526020810190506100f9565b50505050905090810190601f1680156101415780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015b57600080fd5b50610164610a03565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101a4578082015181840152602081019050610189565b50505050905090810190601f1680156101d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156101eb57600080fd5b506101f4610aa1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610234578082015181840152602081019050610219565b50505050905090810190601f1680156102615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561027b57600080fd5b50610284610b3f565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102c45780820151818401526020810190506102a9565b50505050905090810190601f1680156102f15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561030b57600080fd5b50610314610bdd565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610354578082015181840152602081019050610339565b50505050905090810190601f1680156103815780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561039b57600080fd5b5061050e600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610c7b565b005b34801561051c57600080fd5b50610525610cf5565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561056557808201518184015260208101905061054a565b50505050905090810190601f1680156105925780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156105ac57600080fd5b506105b5610d97565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156105f55780820151818401526020810190506105da565b50505050905090810190601f1680156106225780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561063c57600080fd5b50610645610e39565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561068557808201518184015260208101905061066a565b50505050905090810190601f1680156106b25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156106cc57600080fd5b506106d5610ed7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156107155780820151818401526020810190506106fa565b50505050905090810190601f1680156107425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561075c57600080fd5b506108cf600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610f79565b005b3480156108dd57600080fd5b506108e6610ff3565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561092657808201518184015260208101905061090b565b50505050905090810190601f1680156109535780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b606060028054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156109f95780601f106109ce576101008083540402835291602001916109f9565b820191906000526020600020905b8154815290600101906020018083116109dc57829003601f168201915b5050505050905090565b60028054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a995780601f10610a6e57610100808354040283529160200191610a99565b820191906000526020600020905b815481529060010190602001808311610a7c57829003601f168201915b505050505081565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b375780601f10610b0c57610100808354040283529160200191610b37565b820191906000526020600020905b815481529060010190602001808311610b1a57829003601f168201915b505050505081565b60048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bd55780601f10610baa57610100808354040283529160200191610bd5565b820191906000526020600020905b815481529060010190602001808311610bb857829003601f168201915b505050505081565b60038054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c735780601f10610c4857610100808354040283529160200191610c73565b820191906000526020600020905b815481529060010190602001808311610c5657829003601f168201915b505050505081565b8460009080519060200190610c91929190611095565b508360019080519060200190610ca8929190611095565b508260029080519060200190610cbf929190611095565b508160039080519060200190610cd6929190611095565b508060049080519060200190610ced929190611095565b505050505050565b606060018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610d8d5780601f10610d6257610100808354040283529160200191610d8d565b820191906000526020600020905b815481529060010190602001808311610d7057829003601f168201915b5050505050905090565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610e2f5780601f10610e0457610100808354040283529160200191610e2f565b820191906000526020600020905b815481529060010190602001808311610e1257829003601f168201915b5050505050905090565b60008054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610ecf5780601f10610ea457610100808354040283529160200191610ecf565b820191906000526020600020905b815481529060010190602001808311610eb257829003601f168201915b505050505081565b606060038054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610f6f5780601f10610f4457610100808354040283529160200191610f6f565b820191906000526020600020905b815481529060010190602001808311610f5257829003601f168201915b5050505050905090565b8460009080519060200190610f8f929190611095565b508360019080519060200190610fa6929190611095565b508260029080519060200190610fbd929190611095565b508160039080519060200190610fd4929190611095565b508060049080519060200190610feb929190611095565b505050505050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561108b5780601f106110605761010080835404028352916020019161108b565b820191906000526020600020905b81548152906001019060200180831161106e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106110d657805160ff1916838001178555611104565b82800160010185558215611104579182015b828111156111035782518255916020019190600101906110e8565b5b5090506111119190611115565b5090565b61113791905b8082111561113357600081600090555060010161111b565b5090565b905600a165627a7a72305820458843a936d80ffe49dddb0955a0c1d56d0e15f994cd5ce31b386188b2724a790029"
var contractAddress = EthereumAddress("0x11A0c067d7481240dCA57457eff77fc98dEAdE0F")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func callContract(Password: String) {
// Get from address from private key
let formattedKey = Password.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey )!
// ### use [passKey]
let keystore = try! EthereumKeystoreV3(privateKey: dataKey, password: "")!
let keyData = try! JSONEncoder().encode(keystore.keystoreParams)
// let address = keystore.addresses!.first!.address
let address = keystore.addresses!.first!.address
let ethAddress = EthereumAddress(address)
let infura = Web3.InfuraMainnetWeb3()
// 1
let contract = infura.contract(contractABI, at: contractAddress, abiVersion: 2)
// 2
var options = TransactionOptions.defaultOptions
options.from = keystore.addresses!.first!
// 3
let data = Data.init(hex: str)
let transactionIntermediate = contract?.method("getProjectTitle", parameters: [address] as [AnyObject], extraData: data, transactionOptions: options)
// 4
let result = transactionIntermediate!.call(transactionOptions: options)
switch result {
// 5
case .success(let res):
let ans = res["0"] as! Bool
DispatchQueue.main.async {
completion(Result.Success(ans))
}
case .failure(let error):
DispatchQueue.main.async {
completion(Result.Error(error))
}
}
}
}
I get an error for the resultsaying:
"Call can throw, but it is not marked with 'try' and the error is not handled"
and in general I find it really hard to set up the interaction with a smart contract abi.
I am already using the web3swift functionality for sending transactions at it works like a charm.
Maybe someone knows how I can record information on the blockchain and get it using web3swift.

You are close. Starting with the error "Call can throw, but it is not marked with 'try' and the error is not handled" this is caused by trying to call a contract function without using the Try Catch pattern. Do to the way web3 library is designed this pattern is necessary for all write and call methods.
// Incorrect
let result = transactionIntermediate!.call(transactionOptions: options)
// Correct
do {
let result = try transactionIntermediate!.call(transactionOptions: options)
}catch{
print("Error trying to call method \(error)")
}
Additionally, I recommend using the DispatchQueue.main.async along with Promise Kit library when making your contract calls.
ABIs are hard to read and messy, don't recommend using it to help find callable methods and parameters within the contract. Instead I would have the contract open along side Xcode and through the use of either an enum or struct containing all the contract methods that are going to be used.
// Methods available within the contract
enum ContractMethods:String {
case projectContract = "projectContent"
case setContract = "setContract"
case getProjectTitle = "getProjectTitle"
case getProjectLocation = "getProjectLocation"
case getProjectStart = "getProjectStart"
case getProjectEnd = "getProjectEnd"
case getTeamType = "getTeamType"
}
// Usage
ContractMethods.setContract.rawValue
I moved the ABI to a separate file within xcode to keep it clean. Here is link to the file.
Here is a good example to help get you started. Check out my GitHub repo for the improved version.
import UIKit
import web3swift
import PromiseKit
struct Wallet {
let address: String
let data: Data
let name:String
let isHD:Bool
}
struct HDKey {
let name:String?
let address:String
}
var password = "" // leave empty for ganache or use your wallet password
let privateKey = "<PrivateKey>" // Private key of wallet
let walletName = "MyWallet"
let contractAddress = "<ContractAddress>" // 0x11A0c067d7481240dCA57457eff77fc98dEAdE0F
let endpoint = URL(string:"http://127.0.0.1:7545")! // Im using Ganache but it might look like endpoint = URL(string:"https://rinkeby.infura.io/v3/<APIKEY>")!
let abiVersion = 2
class ViewController: UIViewController {
// Mock data used within contract
let projectTitle = "HouseSiding"
let projectLocation = "299 Race Ave. Dacula, GA 30019"
let projectStart = "May 14, 2021"
let projectEnd = "June 15, 2021"
let teamType = "Collaboration"
var web3:web3?
var contract:web3.web3contract?
override func viewDidLoad() {
super.viewDidLoad()
// 1. Create wallet using a private key
let formattedKey = privateKey.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey)!
let keyStore = try! EthereumKeystoreV3(privateKey:dataKey, password: password)!
let keyData = try! JSONEncoder().encode(keyStore.keystoreParams)
let address = keyStore.addresses!.first!.address
let wallet = Wallet(address: address, data: keyData, name: walletName, isHD: false)
// 2. Construct web3 and keystoreManager
do {
web3 = try Web3.new(endpoint)
let data = wallet.data
var keystoreManager: KeystoreManager
if wallet.isHD {
let keystore = BIP32Keystore(data)!
keystoreManager = KeystoreManager([keystore])
}else{
let keystore = EthereumKeystoreV3(data)!
keystoreManager = KeystoreManager([keystore])
}
print(keystoreManager.addresses)
web3!.addKeystoreManager(keystoreManager)
let ethContractAddress = EthereumAddress(contractAddress, ignoreChecksum: true)!
contract = web3!.contract(contractABI, at: ethContractAddress, abiVersion: abiVersion)!
}catch{
print ("Failed to construct contract and/or keystoreManager \(error)")
}
// 3. Create and callout a contract method
//let parameters = [projectTitle,projectLocation,projectStart,projectEnd,teamType] as [AnyObject] // parameters used to created a new project
let parameters = [] as [AnyObject] // no parameters
let response = Promise<Any> { seal in
DispatchQueue.global().async {
// Catch errors within async call
do {
// No extra data for method call
let extraData: Data = Data()
// Options for method call
var options = TransactionOptions.defaultOptions
options.from = EthereumAddress(wallet.address)! // current wallet address
// Leave automatic for gas
options.gasPrice = .automatic
options.gasLimit = .automatic
// Calling get Project title from contract
// NOTE: First call setContract with parameters
let tx = self.contract!.method("getProjectTitle",
parameters: parameters,
extraData: extraData,
transactionOptions: options)
// Depending on the type of call a password might be needed
//if password != nil {
//let result = try tx!.send(password: password)
// seal.resolve(.fulfilled(true))
//}else{
let result = try tx!.call()
// fulfill are result from contract
let anyResult = result["0"] as Any
seal.resolve(.fulfilled(anyResult))
//}
}catch {
// error
seal.reject(error)
}
}
}
response.done({result in
print(result) // Optional(HouseSiding)
})
}
}

Related

How to interact with Smart Contract deployed on Blockchain from Swift app

It is my first time working with smart contracts and my goal is to create a mobile app which can interact with it by calling methods to save or retrieve data. For this reason I have created a very simple contract using Remix and I have also deployed on Rinkeby testnet.
contract Storage {
uint256 number;
function store(uint256 num) public {
number = num;
}
function retrieve() public view returns (uint256){
return number;
}
}
Then I built a SwiftUI app, which has one button. When I press this button, I want to call the store method and save some int number. For example number 9. Therefore I have created a function called write which looks as following:
let myInt = 9
func write() {
let web3 = Web3.InfuraRinkebyWeb3(accessToken: "https://rinkeby.infura.io/v3/a146daf63d93490995823f0910f50118")
let walletAddress = EthereumAddress("0xc65943Fae5a554e7DCF916F8828a73E5f7b1bDCd")! // Your wallet address
let contractMethod = "store" // Contract method you want to write
let contractABI = contractABIString // Contract ABI
let contractAddress = EthereumAddress("0x2826C42354FE5B816c7E21AD9e3B395Ced512C0C")!
let abiVersion = 2 // Contract ABI version
let parameters = [myInt] as [AnyObject]
let extraData: Data = Data() // Extra data for contract method
let contract = web3.contract(contractABI, at: contractAddress, abiVersion: abiVersion)!
var options = TransactionOptions.defaultOptions
options.from = walletAddress
options.gasPrice = .automatic
options.gasLimit = .automatic
do {
contract.write(
contractMethod,
parameters: parameters,
extraData: extraData,
transactionOptions: options)
} catch {
print("error:", error)
}
Unfortunately, when I run this code, nothing is happening. I don't get any error but when I refresh the contract I see that number 9 is not passed.
I am using web3swift library, https://github.com/skywinder/web3swift/tree/master#send-erc-20-token. According to the documentation, it should be fine, but it is not working, therefore I would really appreciate some assistant to make it work or some example projects where I can take a look, because I also couldn't find anything.
I found some other project using JS and I see the people there use their private keys, maybe I also need it, but since it is not shown in the documentation, I was not sure how to use it.
The smart contract interaction requires a few initialisations steps:
Your wallet
KeyStoreManager
web3
Let's assume you already got a Metamask wallet.
Separately you need to have a separate file containing the ABI of the smartContract you want to interact with.
It is an array and having it in a Swift.String is easier.(I won't show you this step)
struct Wallet {
let address: String
let data: Data
let name: String
let isHD: Bool
}
class SmartContractInteraction {
var wallet: Wallet!
var keystoreManager: KeystoreManager!
var web3: web3!
init() {
wallet = initializeWallet()
keystoreManager = getKeyStoreManager()
web3 = initializeweb3()
}
private func initializeWallet() -> Wallet? {
let password = "PasswordMetamask"
let key = "AccountPrivateKey"
let formattedKey = key.trimmingCharacters(in: .whitespacesAndNewlines)
let dataKey = Data.fromHex(formattedKey)!
let name = "Account 1"
do {
let keystore = try EthereumKeystoreV3(privateKey: dataKey, password: password)!
let keyData = try JSONEncoder().encode(keystore.keystoreParams)
let address = keystore.addresses!.first!.address
return Wallet(address: address, data: keyData, name: name, isHD: false)
} catch {
print("wallet init failed: \(error)")
return nil
}
}
private func getKeyStoreManager() -> KeystoreManager {
let data = wallet.data
let keystoreManager: KeystoreManager
if wallet.isHD {
let keystore = BIP32Keystore(data)!
keystoreManager = KeystoreManager([keystore])
} else {
let keystore = EthereumKeystoreV3(data)!
keystoreManager = KeystoreManager([keystore])
}
return keystoreManager
}
private func initializeweb3() -> web3 {
let endpoint = "https://ropsten.infura.io/v3/....."
let web3 = web3swift.web3(provider: Web3HttpProvider(URL(string: endpoint)!)!)
web3.addKeystoreManager(keystoreManager)
return web3
}
func callSmartContract() {
let value: String = "1"
let walletAddress = EthereumAddress(wallet.address)!
let contractAddress = EthereumAddress("SmartContractAddress")!
let contractMethod = "store"
let contractABI = MyContractABI
let abiVersion = 2
let parameters = [9] as [AnyObject]
let extraData: Data = Data()
let contract = web3.contract(contractABI, at: contractAddress, abiVersion: abiVersion)!
let amount = Web3.Utils.parseToBigUInt(value, units: .wei)
var options = TransactionOptions.defaultOptions
options.value = amount
options.from = walletAddress
options.gasPrice = .automatic
options.gasLimit = .automatic
let tx = contract.write(
contractMethod,
parameters: parameters,
extraData: extraData,
transactionOptions: options)!
do {
let password = "MetamaskPassword"
let result = try tx.send(password: password)
print(result)
} catch {
print("Token Balance failed: \(error)")
}
}
}
Like this should work, I think passing the value/option.value in the smartContract call method isn't necessary, but since I don't have your ABI I prefer to not remove anything from how it worked for me. Also I am not sure about the type of the number you pass.
Feel free to edit if this works without :)

Converting app from SQLite.swift to GRDB.swift Second Question

I am continuing the conversion of a number of my apps from SQLite.sift to GRDB.swift.
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB. This was suggested in a reply to my first post on this subject.
struct FigureList: Codable, FetchableRecord
{
let figID: Int64
var notes: String?
var theDescription: String
var obrienNum: String
var manufNum: String?
var catagory: String?
}
This is the piece of code I'm looking for help to redo so it will work with GRDB. My apps use this type of code to build arrays from the database table. Pretty standard process.
static func getFigureList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = Table(gTheCollection)
let figID = Expression<Int64>("FigureID")
let notes = Expression<String>("Notes")
let theDescription = Expression<String>("theDescription")
let obrienNum = Expression<String>("ObrienNum")
let manufNum = Expression<String>("ManufNum")
let theCatagory = Expression<String>("Category")
do {
for figures in try Database.shared.databaseConnection!.prepare(theTable.order(obrienNum)) {
theArray.append(FigureList(figID: figures[figID],
notes: figures[notes],
theDescription: figures[theDescription],
obrienNum: figures[obrienNum],
manufNum: figures[manufNum],
catagory: figures[theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
This is what I have come up with so far. It doesn't produce any warnings or errors but then again I'm pretty sure it's not total correct. Again, thanks in advance for any help.
static func getFigList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = gTheCollection
let figID = Column("FigureID")
let notes = Column("Notes")
let theDescription = Column("theDescription")
let obrienNum = Column("ObrienNum")
let manufNum = Column("ManufNum")
let theCatagory = Column("Category")
do {
try Database.shared.databaseConnection!.read { db in
let figures = try Row.fetchOne(db, sql: "SELECT * FROM = ? ORDER BY ObrienNum", arguments: [theTable])
theArray.append(FigureList.init(figID: figures![figID],
notes: figures![notes],
theDescription: figures![theDescription],
obrienNum: figures![obrienNum],
manufNum: figures![manufNum],
catagory: figures![theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB.
Good. Now it's very simple:
static func getFigList() throws -> [FigureList] {
return try Database.shared.databaseConnection!.read { db in
// SELECT * FROM FigureList ORDER BY ObrienNum
return FigureList.order(Column("ObrienNum")).fetchAll(db)
}
}

SwiftUI: wait for Alamofire request to finish BEFORE executing the code below [duplicate]

This question already has answers here:
How to return value from Alamofire
(5 answers)
Closed 3 years ago.
I'm using an Alamofire request in a function to retrieve an user from a database. Thing is, the function the request is used in is dependant on the request since I'm returning the result as an object of a User class.
Currently, it's always returning the default nil value declared at the top of my function since the code below the request doesn't bother to wait for the request to finish. Basic print statements in the .success or .failure parts prove that (because it doesn't print anything).
I just don't know how to solve it. I've been looking online for a while, and:
either I encounter suggestions on forums on what classes to use without even bothering to explain how to use it
or I get a video that explains an entirely different thing, like handling multiple requests
Can I get any help from you guys? I'm just going to post the code here down below:
The User class:
public class User
{
private var _name : String
public var Naam : String
{
get {return self._name}
set
{
/*
Just a function from a static class which trims a string. It's not really relevant to my problem …
*/
let name : String? = StringOps.trimString(string: newValue)
if name == nil
{
fatalError()
}
self._name = newValue
}
}
/*
* And some fields and properties for:
* - firstname
* - username
* - password
* - email
* They are basically equivalent to the one above
*/
// And a constructor
init(id : Int)
{
self._id = id
self._name = ""
self._firstname = ""
self._username = ""
self._email = ""
self._password = ""
}
}
The request inside my login func in my LoginViewController:
public func login(username: String, password: String)
-> User?
var user : User? = nil
/* Some code that is trimming and checking the input.
The username parameter gets put into a let parameter userName and the same goes for the password parameter. It's Ommitted here.
*/
let parameters : Parameters = [
"userName" : userName!,
"passWord" : passWord!
]
// _loginUrl is a private let String parameter
Alamofire.request(_loginUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil)
.responseJSON
{
(response : DataResponse<Any>) in
switch (response.result)
{
case .success(_):
let data: NSDictionary = (response.value as! NSArray)[0] as! NSDictionary
/* The part commented out isn't working anyways,
and before I started changing the return type of my
function (was a Bool at first),
it worked well enough without it */
/*
if response.result.value != nil
{
/* Convert the JSON to a User object */
let data = (response.value as! NSArray)[0] as! NSDictionary
user = User(id: data["id"] as! Int)
user!.Email = data["e_mail"] as! String
user!.Username = data["username"] as! String
user!.Name = data["name"] as! String
user!.Firstname = data["firstname"] as! String
user!.Password = data["password"] as! String
}
*/
user = User(id: data["id"] as! Int)
user!.Email = data["e_mail"] as! String
user!.Username = data["username"] as! String
user!.Name = data["name"] as! String
user!.Firstname = data["firstname"] as! String
user!.Password = data["password"] as! String
break
case .failure(_):
print(response.result.error as Any)
// _error is also a private var member of LoginViewController
self._error = "Something's wrong with your login credentials!"
user = nil
break
}
}
return user
}
This function gets used in a private function in my LoginView:
struct LoginView
: View
{
// Some unimportant bits
private func logIn()
{
// username and password are #State private vars which are bound to their corresponding Text()
let user : User? = self.controller.login(username: self.username, wachtwoord: self.password)
// other code
}
}
You need to create a completionHandler
By the time your code gets the reference to the User object, is still nil since the response from the server did not come yet.
public func login(username: String, password: String) -> User?
change that to:
public func login(username: String, password: String, completion: #escaping (User?) -> Void)
Then you can do this:
private func logIn() {
self.controller.login(username: self.username, wachtwoord: self.password) { user in
guard let user = user else {
print("Error while retrieving user")
return
}
/// Access your user here
}
}
}

Proper Way to initialize a model class with data gathered from the system (macOS)

I need certain types of system information available to configure my app. I want to achieve this by having a model class which makes the information available. I know how to get the desired information but i am struggling to store this gathered information in the properties of my model class.
i want to be able to:
let sysInfoModel = SysInfoModel()
let installedOsVersion = sysInfoModel.osVersion
The model class should be instantiated from the vieController which needs this information to set up.
I think my whole attempt is wrong to achieve such thing. What is the proper way to do so?
class SysInfoModel {
// Properties
let macModel: String?
let osVersion: String?
// Initialization
init() {
if let actualModel = getMacModel() { //ERROR: 'self' used in method call 'getMacModel' before all stored properties are initialized
self.macModel = actualModel
} else {
macModel = "Hardware model not found"
}
if let actualOsVersion = getOsVersion() { // ERROR: 'self' used in method call 'getOsVersion' before all stored properties are initialized
osVersion = actualOsVersion
} else {
osVersion = "OS Version not available"
}
}
// Returns the hardware model identifier as a string
func getMacModel() -> String? {
var modelIdentifier: String?
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
modelIdentifier = String(data: modelData, encoding: .utf8)
}
IOObjectRelease(service)
return modelIdentifier
}
// Returns the OS Version as a String
func getOsVersion() -> String? {
let osVersion: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion
let osVersionString = String("\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)")
return osVersionString
}
}
Update after matt's answer:
// Properties
var askSystem: AskTheSystem
let macModel: String?
let osVersion: String?
init() {
askSystem = AskTheSystem()
if let actualModel = askSystem.getMacModel() {
self.macModel = actualModel
} else {
macModel = "Hardware model not found"
}
if let actualOsVersion = askSystem.getOsVersion() {
osVersion = actualOsVersion
} else {
osVersion = "OS Version not available"
}
}
I did, there is the possibility to put the functions getMacModel() & getOsVersion() in a separate class called AskTheSystem. Instantiate it in the SysInfoModel.
It works but is this good practice ? i don't see the point in adding another class just for those functions ? What is the proper way to do so ?
I agree with matt, proper is a judgement.
A reasonable solution are lazy instantiated properties, both values are retrieved once when the property is accessed the first time
class SysInfoModel {
// Returns the hardware model identifier as a string
lazy var macModel : String = {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
defer{IOObjectRelease(service)}
guard let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data else {
return "Hardware model not found"
}
return String(data: modelData, encoding: .utf8)!
}()
// Returns the OS Version as a String
lazy var osVersion : String = {
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
return "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
}()
}

How to instantiate a mapped class? (swift - alamofireObjectMapper)

I have this mapped class caled Movie and I make an API request that returns me this type. How can I instantiate this class with the values of my API response?
Movie mapped class:
class Movie: Mappable {
var posterURL : String?
var title : String?
var runtime : String?
var director : String?
var actors : String?
var genre : String?
var plot : String?
var production : String?
var released : String?
var year : String?
var imdbID : String?
var imdbRating : String?
required init?(map: Map) {
}
func mapping(map: Map) {
posterURL <- map["Poster"]
title <- map["Title"]
runtime <- map["Runtime"]
director <- map["Director"]
actors <- map["Actors"]
genre <- map["Genre"]
plot <- map["Plot"]
production <- map["Production"]
released <- map["Released"]
year <- map["Year"]
imdbID <- map["imdbID"]
imdbRating <- map["imdbRating"]
}
}
And in my MovieViewController I'm making the API call and passing the values for my outlet label.
But I would like to instantiate this class by assigning the values ​​obtained in my API call.
func getMovieById() {
let requestURL = "https://www.omdbapi.com/?i=\(String(describing: imdbID!))"
print("URL: \(requestURL)")
Alamofire.request(requestURL).responseObject{ (response: DataResponse<Movie>) in
print("|MovieController| Response is: \(response)")
DispatchQueue.main.async {
let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
spinnerActivity.label.text = "Loading";
spinnerActivity.isUserInteractionEnabled = false;
}
let movie = response.result.value
if let posterURL = movie?.posterURL {
print("Poster URL: \(posterURL)")
let imgStg: String = posterURL
print("---> Image string: \(imgStg)")
let imgURL: URL? = URL(string: imgStg)
let imgSrc = ImageResource(downloadURL: imgURL!, cacheKey: imgStg)
self.movPosterImageView.layer.cornerRadius = self.movPosterImageView.frame.size.width/2
self.movPosterImageView.clipsToBounds = true
//image cache with KingFisher
self.movPosterImageView.kf.setImage(with: imgSrc)
}
if let title = movie?.title {
print("Title: \(title)")
self.movTitleLabel.text = title
}
if let runtime = movie?.runtime {
print("Runtime: \(runtime)")
self.movRuntimeLabel.text = runtime
}
if let genre = movie?.genre {
print("Genre: \(genre)")
self.movGenreLabel.text = genre
}
if let plot = movie?.plot {
print("Plot: \(plot)")
self.movPlotTextView.text = plot
}
if let rating = movie?.imdbRating {
print("Rating: \(rating)")
self.movRatingLabel.text = rating
}
if let director = movie?.director {
print("Director: \(director)")
self.movDirectorLabel.text = director
}
if let production = movie?.production {
print("Production: \(production)")
self.movProductionLabel.text = production
}
if let actors = movie?.actors {
print("Actors: \(actors)")
self.movActorsLabel.text = actors
}
if let released = movie?.released {
print("Released in: \(released)")
self.movReleasedLabel.text = released
}
DispatchQueue.main.async {
MBProgressHUD.hide(for: self.view, animated: true)
}
}//Alamofire.request
}//getMovieByID()
It would be something like
let movieDetails: Movie = Movie(plot = movie?.plot, title = movie?.title, ...)
How can I do this with a mappable class?
Update
I'm trying to organize this things and also I'll have to reuse code, so did this inside functions seems better for me. So, I started separating the API call putting like this:
file: OMDB.swift
import Foundation
import Alamofire
import AlamofireObjectMapper
func getMovieIdFromAPI(imdbID: String, completionHandler: #escaping (Movie) -> () ) {
let requestURL = "https://www.omdbapi.com/?i=\(imdbID)"
print("|getMovieIdFromAPI| URL: \(requestURL)")
Alamofire.request(requestURL).responseObject{ (response: DataResponse<Movie>) in
print("|Alamofire request| Response is: \(response)")
if let movieResult = response.result.value{
completionHandler(movieResult)
}
}
}
Next step, I'm trying to create a MovieDAO, and here I'll have to instantiate my object, right? So, in the same file as my Movie class is, I've created a MovieDAO class with this function:
class MovieDAO {
func getMovieDetailed<Movie: Mappable>(imdbID: String, completionHandler: #escaping (Movie) -> ()) {
getMovieIdFromAPI(imdbID: imdbID, completionHandler: {
(movieResult) in
let mapper = Mapper<Movie>()
let movieDetailed = mapper.map(movieResult)!
completionHandler(movieDetailed)
})
}
}
But I didn't understood very well the answer and the xcode gives me an error in
let movieDetailed = mapper.map(movieResult)!
^Error: Argument labels '(_:)' do not match any available overloads
Could you explain how can I use the answer given in this case?
ObjectMapper is what helps you get an instance of the model class, with the property values set as per your API response. You will need to do the last step where in you tell ObjectMapper to do the 'mapping' procedure with the json you provide it.You can use this generic method to parse response for any Mappable class
static func parseModel<Model: Mappable>(modelResponse modelResponse: AnyObject, modelClass: Model.Type) -> Model? {
let mapper = Mapper<Model>()
let modelObject = mapper.map(modelResponse)!
return modelObject
}