Where to put logic shared by multiple view controllers - ios

I'm working on an iPhone app (Objective-C) which has barcode scanning functionality on many of its screens. The user can tap a control to recognize different barcodes and navigate to different screens depending on what type of barcode is recognized. The majority of the logic does not depend on which screen they initiated the scan from... As such, I don't want to duplicate the code in each view controller, but am uncertain where the best place for it is. It requires the user tapping on a detection rectangle, so it does need to be able to handle these events. Many thanks!

You could use a singleton
class DataStore {
static let sharedDataStore = DataStore()
func scanBarcode() {
//logic
}
}
calling the function in another View Controller
class viewController: UIViewController {
let shared = DataStore.sharedDataStore
override func viewDidLoad() {
//calling the function
shared.scanBarcode()
}

Related

UITableViewController as data entry screen (iOS, Swift) a la Calendar app "New Event" screen

My app tracks events. Users can create new events or join existing ones. Events have Title, Location, Start Date, optional End Date, etc.
I have a UITableViewController to show the list of events, bog standard with one custom cell.
My data entry screen (see Image 2) to create an event is also a UITableViewController, similar to the Apple iOS Calendar app's "New Event" screen (see Image 1). I created multiple Prototype Cells (one with a UITextField, another with a UIDatePicker, another with a UICollectionView, etc.). The screen, as such, is working fine, but...
I'm trying to create an unwind segue like in this tutorial from Apple in the section "Create an Unwind Segue". Step 7 - no problem. Step 8 - eek!
In the tutorial, there are simple controls to collect the data, create a "Meal", add it to the array of meals and return to the list of Meals. But, I have a UITableViewController.
I have no idea how to collect the data in order to create my Event, add it to the array, etc.
Do I need to redesign my UI without using a UITableViewController? It seems like the perfect UI for this scenario.
you can use Delegates to get the data from the Table xib to whichever controller you want,Hope this helps
1.Add this method on top of your xib's viewcontroller file
protocol ViewModelDelegate
{
func sendValue(from PassData: String?)
}
class XibViewController: UITableViewCell {
var controller:HomeViewController()
override func awakeFromNib() {
super.awakeFromNib()
controller.delegate = self
}
//call this method on any button action or save
controller.sendValue(from PassData:String?)
}
Create a function where you want to fetch all data into viewcontroller.
class:HomeViewController:UITableViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
}
func sendValue(from PassData:String?)
{
//code to get the data from picker or textfield
}
}

Should ViewController-Presenter-Interactor have one to one relationship

I am reading about VIPER and my understanding is- generally a viewController is related to one presenter and one presenter talks to one Interactor.
But, what if we have master-details pages or list-detail pages. To display list of items, I would have one controller/Presenter to display list and another controller/presenter to display details. And FetchList and FetchDetail should belong to same interactor.
If these two presenters communicate to this interactor, they would have to implement both the methods FetchList and FetchDetail. And one of these two method's implementation would be empty.
You should have two separate VIPER modules: MainItems and DetailedItems.
Read this post (https://www.ckl.io/blog/best-practices-viper-architecture) and see how to use delegates to send data between VIPER modules. Note that FetchList and FetchDetail should belong to different interactor:
// 1. Declare which messages can be sent to the delegate
// ProductScreenDelegate.swift
protocol ProductScreenDelegate {
//Add arguments if you need to send some information
func onProductScreenDismissed()
func onProductSelected(_ product: Product?)
}
// 2. Call the delegate when you need to send him a message
// ProductPresenter.swift
class ProductPresenter {
// MARK: Properties
weak var view: ProductView?
var router: ProductWireframe?
var interactor: ProductUseCase?
var delegate: ProductScreenDelegate?
}
extension ProductPresenter: ProductPresentation {
//View tells Presenter that view disappeared
func onViewDidDisappear() {
//Presenter tells its delegate that the screen was dismissed
delegate?.onProductScreenDismissed()
}
}
// 3. Implement the delegate protocol to do something when you receive the message
// ScannerPresenter.swift
class ScannerPresenter: ProductScreenDelegate {
//Presenter receives the message from the sender
func onProductScreenDismissed() {
//Presenter tells view what to do once product screen was dismissed
view?.startScanning()
}
...
}
// 4. Link the delegate from the Product presenter in order to proper initialize it
// File ScannerRouter.swift
class ProductRouter {
static func setupModule(delegate: ProductScreenDelegate?) -> ProductViewController {
...
let presenter = ScannerPresenter()
presenter.view = view
presenter.interactor = interactor
presenter.router = router
presenter.delegate = delegate // Add this line to link the delegate
...
}
}
My understanding is that you have one view/view-controller and presenter per screen and then one interactor per use case, which probably means more than one per screen. This is good practice from the point of view of Single Responsibility Principle and therefore aids testing. But sometimes a concession is made and an interactor handles multiple uses cases.

Sharing information between ViewContollers

I am developing a game using Sprite and the game works perfectly fine. I am trying to add in a feature that allows you to change things on that GameView. But the buttons for the feature are on a different ViewController. How would I be able to have the code be implemented into a different view controller?
This is the code i want the button to implement
for touch: AnyObject in touches {
// Get the location of the touch in this scene
let location = touch.location(in: self)
// Check if the location of the touch is within the button's bounds
if button.contains(location) {
Ghost.texture = SKTexture(imageNamed:"Ghost2")
}
}
And here is the button
#IBAction func button(_ sender: AnyObject) {
}
But the button is on ViewController1 and the object that i want to change is on ViewController2 (GemScene)
Take a look to this question:
Passing Data between View Controllers (One of the answers shows Swift 3 syntax.)
Anyway, what I personally have done in these cases is to use NotificationCenter, which provides loosely coupled messages even between different ViewControllers. You can take a look here: NSNotifications in Swift 3

How to subscribe to delegate events globally?

I have a custom delegate that triggers certain events. For context, it's a bluetooth device that fires events arbitrarily. I'd like my view controllers to optionally subscribe to these events that get triggered by the device delegate.
It doesn't make sense that each view controller conforms to the custom delegate because that means the device variable would be local and would only fire in that view controller. Other view controllers wouldn't be aware of the change. Another concrete example would be CLLocationManagerDelegate - for example what if I wanted all view controllers to listen to the GPS coordinate changes?
Instead, I was thinking more of a global delegate that all view controllers can subscribe to. So if one view controller triggers a request, the device would call the delegate function for all subscribed view controllers that are listening.
How can I achieve this architectural design? Are delegates not the right approach? I thought maybe NotificationCenter can help here, but seems too loosely typed, perhaps throwing protocols would help makes things more manageable/elegant? Any help would be greatly appreciated!
You could have an array of subscribers that would get notified.
class CustomNotifier {
private var targets : [AnyObject] = [AnyObject]()
private var actions : [Selector] = [Selector]()
func addGlobalEventTarget(target: AnyObject, action: Selector) {
targets.append(target)
actions.append(action)
}
private func notifyEveryone () {
for index in 0 ..< targets.count {
if targets[index].respondsToSelector(actions[index]) {
targets[index].performSelector(actions[index])
}
}
}
}
Naturally, you'd have to plan further to maintain the lifecycle of targets and actions, and provide a way to unsubscribe etc.
Note: Also ideal would be for the array of targets and actions to be an of weak objects. This SO question, for instance, deals with the subject.
• NotificationCenter is first solution that comes in mind. Yes, it is loosely typed. But you can improve it. For example like this:
extension NSNotificationCenter {
private let myCustomNotification = "MyCustomNotification"
func postMyCustomNotification() {
postNotification(myCustomNotification)
}
func addMyCustomNotificationObserverUsingBlock(block: () -> ()) -> NSObjectProtocol {
return addObserverForName(myCustomNotification, object: nil, queue: nil) { _ in
block()
}
}
}
• Second solution would be to create some shared object, which will store all delegates or blocks/closures and will trigger them when needed. Such object basically will be the same as using NotificationCenter, but gives you more control.

One class instantiation swift share between View Controllers

I'm having a bit of trouble getting this right. What i have is a NavigationController with several View Controllers. In the first View Controller i instantiate a new class, GPSTrackingManager:
var tracking = GPSTrackingManager()
Because i need GPS data and functions available in all view controllers after this UINavController. I'm calling this already in the first VC so the GPS can get a early fix. How can i pass this tracking object to the new view controllers? I tried several things in the prepareForSegue, but i'm out of ideas currently. If i put the above code in the new VC then i have the startUpdatingLocation() running 2 times. If you can point me in the right direction that would be greatly appreciated!
I think the most appropriate solution (without going too overboard, because there are certainly more thorough ways to go about this problem) is to separate the gps management into its own singleton class, and to subscribe to a trackerUpdated protocol for updates.
import Foundation
import placeWhereGPSTrackingManagerIs
protocol GPSManagerDelegate {
func didUpdateTracker(tracker: yourTrackerObject)
}
let _GPSManagerSharedInstance = GPSManager()
class GPSManager {
weak var delegate: GPSManagerDelegate?
let tracker: yourTrackerObject?
class var sharedInstance:GPSManager {
return _GPSManagerSharedInstance
}
init() {
tracker = GPSTrackingManager()
tracker.startUpdatingLocation()
}
func updateTracker(newInformation) // you'll want to pass whatever information your tracker needs here
{
tracker.trackerProperty = newInformation
self.delegate?.didUpdateTracker(tracker)
}
}
Swift singleton explanation
Using a dispatch_once singleton model in Swift
Now, whatever startUpdatingLocation does could call updateTracker in this class with the new information it needs to update the tracker. You could also migrate that logic to live inside of here (which I recommend doing in this case) so everything is contained in one class.
Any view controller interested in the information can subscribe to the didUpdateTracker protocol method, which will pass the tracker through to that view controller
import UIKit
class controllerOne: UIViewController, GPSManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
GPSManager.sharedInstance.delegate = self // set the GPSManager delegate
if let tracker = GPSManager.sharedInstance.tracker {
// if the tracker exists, we assign it to the "tracker" variable, and use as needed
// setup page with tracker
}
}
func didUpdateTracker(updatedTracker: yourTrackerObject) {
// protocol method that will be called from the gpsmanager everytime it updates
// do whatever updates to the page with the
}
}
As you can only have one GPSManager and its tracker property cannot be changed, it is always safe to ask the GPS manager for this tracker, which only calls startUpdatingLocation() on its initialization
You could also post notifications as #shesh nath mentioned, which is a perfectly valid way to broadcast changes if you prefer it to the delegation method. Either way you approach it, I would recommend separating the tracker from the view controller so that the view controllers are not the ones in charge of managing the trackers state.
There are several ways to achieve it like
1) using Key-Value observer
2) Notification
Example of Notification is on your first viewController post notification and pass your data to this notification.
NSDictionary *data;
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateBeaconStateOnDetailScreen" object:data];
and on left-over all viewcontroller register listener for this notification on viewdidload example.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateBeaconState:)name:#"updateBeaconStateOnDetailScreen" object:nil];
and your method which will be called by this notification is
-(void) updateBeaconState:(NSNotification *) notification
{ //notification raised from BeaconList with updated Beacon State
NSDictionary *dictUpdatedBeacon=[notification valueForKey:#"object"];
}

Resources