How can I send fetched data to my ViewController? - swift

Heres a caption of my API call:
So, I've got the abilities of the pokemons I needed, but now, idk how to get that data out of my Service class (where I'm doing all the parsing), and send it to my InfoViewController.
My purpose is to fetch that data on some label, and then show the ability names for every poke, according to their ID. Here is a caption of my app:
I wanna add an "Ability" label below Weight, and that's where I wanna assign the data. I have a whole CollectionView with all the pokemons, and the goal is assign the correct ability for each one of them.
I'm kinda struggling for a practical (and less verbose) way to reach this.
I apreciated every comment, any advice and suggestion too. Thanks!
EDIT: Heres my code:
extension InfoController: ServiceDelegate {
func finishedWithPokemonAbilities(abilities: [String], id: Int) {
self.abilities = abilities
self.ids = id
print(abilities)
}
}

You can create a custom Protocol(could call it PokemonServiceDelegate as an example) that your InfoViewController would inherit and implement. On your service object(I'm using PokemonService in the example) create a property with a type of PokemonServiceDelegate and set that property to the view controller that you want to receive the data. After the service finishes fetching the data, update the delegate by passing the data in the function declared in the protocol.
// Protocol your view controller will inherit
protocol PokemonServiceDelegate {
// Function your view controller will implement
func finishedWithPokemonAbilities(abilities: [String])
}
class InfoViewController: UIViewController {
// Reference to the service that makes the request
var service: PokemonService
override func viewDidLoad() {
...
// Set the delegate of the service to self
service.delegate = self
...
}
}
extension InfoViewController: PokemonServiceDelegate {
// Implement the protocol
func finishedWithPokemonAbilities(abilities: [String]) {
// Do something with their abilities here
}
}
struct PokemonService {
var delegate: PokemonServiceDelegate?
// The function that you call to get your abilities
func someUpdateFunc() {
...
let abilities = json[abilities].arrayValue.map {$0["ability"]["name"].stringValue}
delegate?.finishedWithPokemonAbilities(abilities: abilities)
...
}
}

Related

How to create separate data mapping file with function names in Swift

I have a macOS app that I'm creating in Swift and I have integrated an external HID device that has a number of controls on it.
The HID part is done where I am receiving all of the hid commands from the device and I am trying to create a mapping file where I can maintain the HID key mappings in a separate swift file.
All I want in that file is the data and what I want to do is this;
raw hid data is received from HID device (In ViewController)
Lookup the function name assigned to this hid data (In separate file)
Run the function that is mapped to that key. (Function located in the main ViewController)
So far I have the external swift file setup with all of the mapping and that all works fine but my issue is when I try to call the looked up function in the ViewController, it says the function can't be found in the scope.
Initially I thought I would use a delegate but the external file isn't a viewcontroller, just a separate swift file so I don't know if I can do that?.
I've tried searching but everything I've found is calling a function from another ViewController which I'm not. It's very possible I'm not using the best approach and my goal is to just keep all of the mapping in a separate file as there is a lot and it woudl be easier to maintain.
Any suggestions are appreciated.
This is one way to achieve this. It can get tedious. You can totally skip writing out a separate protocol for the delegate, but this is cleaner design.
protocol HIDMessageDelegate: AnyObject {
// example messages
func message1()
func message2()
func message3()
}
class HIDMessageParser {
static weak var delegate: HIDMessageDelegate?
static func parseHIDMessage() {
var condition = 0
// this is where your switch statement will go and you'll parse things and call the relevant delegate method
switch (condition) {
default:
delegate?.message1()
}
}
}
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
HIDMessageParser.delegate = self
}
}
extension MyViewController: HIDMessageDelegate {
func message1() {
}
func message2() {
}
func message3() {
}
}
You can simply create a UIViewController as the external file and add it as a property to the main ViewController.
In the external file add this.
#IBOutlet var uiViewController: UIViewController!
In the ViewController add this.
var externalFileViewController: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
externalFileViewController = externalFileViewController?.loadView()
// If we have an object then load it
if let viewController = externalFileViewController {
viewController.view.frame = view.frame
viewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(viewController.view)
uiViewController = viewController
}
}
Now in the viewController look up the functions to be called from the external file and call them using the function name.
The functions are defined in the external file using #IBAction.
Let me know if you have any questions.

Sending data using protocols

I have issues with using protocols to send data back to previous controller. I have studied SO questions and guides, but for some reason my data doesn't get transferred back.
In my second class I create data, that is later being sent back to first class:
protocol ImageEditorDelegate {
func sendImage(image: UIImage, id: String)
}
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didPressSave(_ sender: UIButton) {
delegate?.sendImage(image: finalImage, id: imageThatWasSelected)
self.dismiss(animated: true, completion: nil)
}
}
And in my receiving class I have:
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
override func viewDidLoad() {
super.viewDidLoad()
imageEditor?.delegate = self
}
func sendImage(image: UIImage, id: String) {
print("Receiving images", image, id)
switch id {
case "1":
selectedImages[1] = image
productImage1.image = image
case "2":
selectedImages[2] = image
productImage2.image = image
case "3":
selectedImages[3] = image
productImage3.image = image
default:
break
}
}
}
But nothing happens, this func never gets called. I think my delegate is nil, or so, but how could I fix this issue? I have Also, I'm using VIPER as architecture with slightly customized segues, may this be the issue? I have tried simple segues, but had same issue.
I understand that this is rather simple question, but I couldn't understand what I doing wrong after I have read articles about protocols.
Thanks for your help!
What you're doing is very wrong. You have two view controllers with property references to one another:
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
}
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
}
Those are not weak references, so if you ever do get this to work — that is, if you ever arrange things so that the NewProductViewController's imageEditor is a PhotoEditorViewController whose delegate is that NewProductViewController — you will have a nasty retain cycle and a memory leak.
This suggests that you have not understood the protocol-and-delegate pattern. Only the presented view controller should have a delegate property pointing back to the presenter, and it should be weak. The presenter does not need any property pointing to the presented view controller, because it presents it.
you need to instantiate your photoEditor, like
photoEditor = PhotoEditorViewController()
before attempting to set its delegate.
you dont' have to do this next part, but I'd suggest making the delegate variable a weak variable to avoid any retain issues, like so
weak var delegate: ImageEditorDelegate?
and you'll need to mark the protocol as class like so
protocol ImageEditorDelegate : class {

Inter-thread inter-object communication in Swift 3 with Cocoa

My program consists of three relatively-distinct areas: listening on a network for new state, performing network actions, and updating the UI. So respectively I want three classes: StateListener, ActionSender, and ViewController, each chugging along on separate threads.
Would that it were so simple -- the three need to interact. Some states discovered by the StateListener require Actions to be sent by the ActionSender or the UI to be updated by the ViewController. Some responses to Actions require the UI to be updated by the ViewController. Some UI actions require Actions to be performed by the ActionSender.
Currently I do something like this (pseudocode):
/* ViewController.swift */
class ViewController : blah
{
//...
func buttonPressed()
{
// ?! Need to do an action here but I can't
// because actionSender is initialised below...
}
func viewDidLoad()
{
let actionSender = ActionSender(m_view: self)
let actionQueue = OperationQueue()
let stateListener = StateListener(m_view: self,
m_actionSender: actionSender,
m_actionQueue: actionQueue)
let stateQueue = OperationQueue()
stateQueue.addOperation(stateQueue.listen())
}
}
/* StateListener.swift */
class StateListener
{
// ...
func listen()
{
while true
{
var state = waitForNewState()
if shouldActOn(state)
{
m_actionQueue.addOperation(m_actionSender.act())
}
}
}
}
/* ActionSender.swift */
class ActionSender
{
// ...
func act()
{
var reply = sendAction()
OperationQueue.main.addOperation(m_view.m_textBox.append(reply))
}
}
This is fairly hellish and doesn't even do what I want it to do, because I can't have the ViewController perform actions (ActionSender's require a ViewController reference to update the view after the action, but I tried initialising the ActionSender within ViewController.init and I got bizarre errors to do with a Code.init that I hadn't implemented...). I want to get above ViewController and initialise all these OperationQueues and objects wherever ViewController gets initialised, but I can't find where that is...
What I've done above is basically object-reference injection of each object and OperationQueue. I know there are other ways of doing this (a hierarchy of callbacks, NSNotifications) but I'm unsure of which is best.
My question is in two parts:
What is the best (i.e., fastest, easiest to implement and maintain, most idiomatic in Swift) way to get the inter-object and inter-thread communication I desire?
I currently get things going from ViewController's viewDidLoad function, which seems awful (and means I can't get a 'higher-up' perspective of the ViewController. Where should this stuff go? AppDelegate advertises itself as the 'program startup' area, but I can't access the ViewController from there... XCode seems to have hidden the startup of my app from me!
I really appreciate your responses!
This post greatly helped with question 2 and the problem of passing self as a parameter to a data member in an initialiser : http://blog.scottlogic.com/2014/11/20/swift-initialisation.html
Specifically, I can use this pattern:
class Foo : blah
{
var m_bar : Bar!
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar = Bar(m_foo: self)
m_bar.doYourThing()
}
}
The blog prefers the following, which I feel I should add here out of gratitude to the author, though I prefer the above.
class Foo : blah
{
lazy var m_bar : Bar = {
return Bar(m_foo: self) // notice I can pass in self
}
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar.doYourThing()
}
}

strange optional error in Swift

Okay, so basically I am following a tutorial on udemy on how to create a chat with Backendless and Firebase. However, I prefer not to use Backendless, because I don't want to rely on 2 providers - so I want to stick to Firebase only. Therefore, I am currently converting my code to Firebase.
I have a view controller that displays a unique page for each UID - from a database that I have. The UID is stored as a String, and is assigned upon a segue from another table view controller (this works fine). After that, I fetch the data that I want from the user, with the UID. I have a "Start Chat" button that is supposed to create a new chat.
In this tutorial, the tutor has set a protocol (delegate) that is triggering another function from another view controller. This is what it looks like:
protocol ChooseUserDelegate {
func createChatroom(withUser: String)
}
var delegate: ChooseUserDelegate!
and in my chat #IBAction, I have this code:
#IBAction func StartChat(sender: AnyObject) {
let userID = uid
if let theId = userID as? String {
delegate.createChat(String(theId))
}
}
(The code above is all in the same VC.).
In another view controller, where the createChat() function is stored, is the following code:
class AnotherVC UIViewController, UITableViewDelegate, UITableViewDataSource, ChooseUserDelegate{
func createChat(withUser: String) {
print(withUser)
}
}
The problem is that I can't get to call createChat(), because of an optional error (unwrapping) on the delegate.createChat(String(theId)).
Edit: Even with a "" input, I get an error. I am really confused now. Is it something wrong with my delegate?
The only part of your code that is optional is delegate (because you correctly unwrapped userID). Therefore, the error must be due to delegate being nil. Make sure that you set delegate before calling StartChat().
The line var delegate: ChooseUserDelegate! does not initialize a delegate. When you write ChooseUserDelegate! you are only defining the type of the delegate variable. It is automatically set to nil. To initialize a new instance of ChooseUserDelegate you would need to write something like:
var delegate: ChooseUserDelegate! = ChooseUserDelegate()
There are a few other ways you could clean up your code. Method names should be llamaCase, not CamelCase, so you should rename StartChat() to startChat() (be sure to reconnect in interface builder). The body of that method has three different names for the same variable, uid. See how simple it could be:
#IBAction func startChat(sender: AnyObject) {
if let uid = uid as? String {
delegate.createChat(uid)
}
}
if let theId = userID {
delegate.createChat(String(theId))
}

Implement Alamofire into DDD structure

Qustion
I have a following structure
class UITableViewController (Presentation) -> class Contents(Domain) -> class API(Infrastructure)
Contents class gets raw data via API class and forms contents and then passes to UITableViewController.
I would like to use Alamofire to do Networking in API class.
I’ve looked through stackoverflow and I only found examples that UITableViewController directly accesses API class. Direct access from Presentation layer to Infrastructure layer is something we should not do.
Like this
How to return value from Alamofire
How do I achieve implementing Alamofire into DDD structure?
I want to achieve something like this
UITableViewController
class MyTableViewController: UITableViewController {
var contents: Contents?
override func viewDidLoad() {
super.viewDidLoad()
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
self.contents = Contents.get("MyContents")
self.tableView.reloadData()
}
}
}
Contents
class Contents: NSObject {
static func get(contentsName: String) -> Contents {
let data = MyAPI.getRequest("https://xxxx.com/myContents")
// Form contents
let contents = ContentsFactory(data)
return contents
}
}
API
class MyAPI: NSObject {
static getRequest(url) -> NSData {
// Get and return data using alamofire
}
}
You need to deal with the get request being run in the background, whereas the instance of Contents will be returned immediately. In the completion handler for the Alamofire request, you will need it to call back to the Contents instance, which will then call back to the ViewController.
Therefore your Contents class needs to have a completion closure passed on the get function, along the lines of:
static func get(contentsName: String, completion: (() -> Void)?) -> Contents {
This closure will have to be passed down to the API (or through other techniques), so it can ultimately be called when the Alamofire request completes. Also, rather than have the class method do all the background work, it would be better handled in the Contents instance you create.
Then you will call using:
self.contents = Contents.get("MyContents", completion({
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.tableView.reloadData()
}
}