How to shorten multiple classes with the same code implementation - swift

For example I have these 5 TVC's whose names are OneTVC, TwoTVC, ..., FiveTVC and all of the has implemented a protocol called SomeProtocol
protocol SomeProtocol {
var sourceViewController: UIViewController! { get set }
weak var bottomView: SomeCustomView! { get set }
func configure(sourceViewController sourceViewController: UIViewController)
}
all TVC's have implemented SomeProtocol with the same codes
class OneTVC: UITableViewCell, SomeProtocol {
var sourceViewController:UIViewController!
#IBOutlet weak var bottomView: SomeCustomView!
func configure(sourceViewController sourceViewController: UIViewController) {
self.sourceViewController = sourceViewController
bottomView.btnOne.addTarget(.... #selector(self.doSomething(_:)))
bottomView.addTarget(.... #selector(self.doAnother(_:)))
}
}
/* all these codes */
extension OneTVC {
func doSomething(sender:UIButton) {
// same codes as TwoTVC ..., FiveTVC
}
func doAnother(sender:UIButton) {
// same codes as TwoTVC ..., FiveTVC
}
}
/* all these codes */
What I want to be able to do is not write codes that are enclosed in /* all these codes */
Using protocols I may be able to do something like this
protocol SomeProtocol {
var sourceViewController: UIViewController! { get set }
weak var bottomView: SomeCustomView! { get set }
func configure(sourceViewController sourceViewController: UIViewController)
}
extension SomeProtocol {
func doSomething(sender:UIButton) {
// some custom implementation
}
func doAnother(sender:UIButton) {
// some custom implementation
}
}
But I can't do this cause this protocol needs to be declared as an Objective-C protocol like so #objc protocol SomeProtocol because of addTarget(_:, _:, _:) but by doing this I can't use these optional or implicit operators ! or ? for declaring Variables inside the protocol
If I create a superclass I can't override variables that I need to be #IBOutlets for example.
class SomeSuperClass: UITableViewCell, SomeProtocol {
var sourceViewController:UIViewController!
#IBOutlet weak var bottomView: SomeCustomView!
func configure(sourceViewController sourceViewController: UIViewController) {
self.sourceViewController = sourceViewController
bottomView.btnOne.addTarget(.... #selector(self.doSomething(_:)))
bottomView.addTarget(.... #selector(self.doAnother(_:)))
}
}
class OneTVC: SomeSuperClass {
#IBOutlet override weak var bottomView: SomeCustomView! // < this here makes an error
// .. rest of the codes
}
What do I do?

Related

Swift UIViewController subclass variable in protocol [duplicate]

This question already has an answer here:
Swift: How can I make a function with a Subclass return type conform to a protocol, where a Superclass is defined as a return type?
(1 answer)
Closed 1 year ago.
My idea is to have a common router that I could use for common method that I use across my app. But I get this error and I want to keep weak var viewController as a HomeViewController
How can I get it to work ?
Type 'HomeRouter' does not conform to protocol 'CommonRoutingLogic'
import UIKit
class HomeViewController: UIViewController {
var router: HomeRoutingLogic?
func detailsClicked() {
router?.goToDetails()
}
}
class HomeRouter: CommonRoutingLogic {
weak var viewController: HomeViewController?
init(viewController: HomeViewController?) {
self.viewController = viewController
}
}
protocol HomeRoutingLogic: CommonRoutingLogic {
func displayHomeGreetings()
}
protocol CommonRoutingLogic {
var viewController: UIViewController? { get set }
func goToDetails()
}
extension CommonRoutingLogic {
func goToDetails() {
// goto details
}
}
Seems that you are not implementing the goToDetails() method.
Maybe changin this:
extension CommonRoutingLogic {
func goToDetails() {
// goto details
}
}
to this?
extension HomeRouter : CommonRoutingLogic {
func goToDetails() {
// goto details
}
}

closure through typealias swift not working

why does typealias closure not transmit data and output nothing to the console? How to fix it?
class viewModel: NSObject {
var abc = ["123", "456", "789"]
typealias type = ([String]) -> Void
var send: type?
func createCharts(_ dataPoints: [String]) {
var dataEntry: [String] = []
for item in dataPoints {
dataEntry.append(item)
}
send?(dataEntry)
}
override init() {
super.init()
self.createCharts(abc)
}
}
class ViewController: UIViewController {
var viewModel: viewModel = viewModel()
func type() {
viewModel.send = { item in
print(item)
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("hello")
type()
}
}
I have a project in which a similar design works, but I can not repeat it
The pattern is fine, but the timing is off.
You’re calling createCharts during the init of the view model. But the view controller is setting the send closure after the init of the view model is done.
Bottom line, you probably don’t want to call createCharts during the init of the view model.
Possible solution is to create custom initializer:
class viewModel: NSObject {
...
init(send: type?) {
self.send = send
self.createCharts(abc)
}
}
class ViewController: UIViewController {
var viewModel: viewModel = viewModel(send: { print($0) })
...
}

Workaround for simple protocol inheriting conformance

In our app, we have a service that helps us decide which Modal UIVIewController should we present next. Every ModalVIewController has common function such as dismiss() but also a specific function it implements. So that's what we tried:
The base protocol that is common to all VC's base functions.
protocol ModalScreenDelegate: AnyObject {
func modalScreenWantsToDissmiss(_ modalScreen: ModalScreen)
}
A base protocol that every UIViewController implements
protocol ModalScreen: UIViewController {
var delegate: ModalScreenDelegate? { get set }
}
Now we create a protocol with specific-implementation of ModalScreenDelegate base protocol like so:
protocol ShareToFacebookDelegate: ModalScreenDelegate {
func someCustomMethod()
}
And assign it to:
class ShareToFacebookViewController: UIViewController, ModalScreen {
weak var delegate: ModalScreenDelegate? // **WORKS**
weak var delegate: ShareToFacebookDelegate? // **DOESN'T WORKS**
}
If I'm trying to use ShareToFacebookDelegate to instead of ModalScreenDelegate the compiler throws an IDE error saying I have to change it back to ModalScreenDelegate.
Why wouldn't it work? It's ShareToFacebookDelegate conforms to ModalScreenDelegate.
Any help would be highly appreciated.
Thank you!
UPDATE Based on Alexandr Kolesnik:
Your method works. But when I try to "fetch" the correct VC within the service under one method like so:
func fetchModal<T: ModalScreen & UIViewController>() -> T? {
return AddInstagramViewController.create() as? T
}
And then have a coordinator that wants to get this vc:
guard let currentModalViewController vc = modalScreenSupplierService.fetchModal() else {
return
}
I'm getting:
Generic parameter 'T' could not be inferred
And I can't really say what T will be, all I know that it's going to conform to UIViewController & ModalScreen. Is it solvable?
If I understood you correctly you can use generic types to manage the problem. Look through the code below. Hope it helps
protocol ModalScreenDelegate: AnyObject {
typealias T = ModalScreenDelegate
func modalScreenWantsToDissmiss(_ modalScreen: T)
}
protocol ShareToFacebookDelegate: ModalScreenDelegate {
func someCustomMethod()
}
protocol ModalScreen: UIViewController {
associatedtype T
var delegate: T? { get set }
}
class ShareToFacebookViewController: UIViewController, ModalScreen {
typealias T = ShareToFacebookDelegate
weak var delegate: T?
override func viewDidLoad() {
super.viewDidLoad()
delegate?.someCustomMethod()
}
}
UPDATE:
class AddInstagramViewController: SuperVC {
typealias T = ShareToFacebookDelegate
private var instaDelegate: ShareToFacebookDelegate?
override var delegate: ModalScreenDelegate? {
set {
instaDelegate = newValue as? ShareToFacebookDelegate
}
get {
return instaDelegate
}
}
static func create() -> AddInstagramViewController {
return AddInstagramViewController()
}
}
class SuperVC: UIViewController, ModalScreen {
typealias T = ModalScreenDelegate
var delegate: T?
}
class Supplier {
func fetchModal<M: ModalScreen>() -> M? { return AddInstagramViewController.create() as? M }
}
class SupplierImpl {
let modalScreenSupplierService: Supplier? = nil
func goto() {
guard
let vc: SuperVC = modalScreenSupplierService?.fetchModal()
else {
return
}
}
}
This solution:
protocol ModalScreenDelegate: AnyObject {
func modalScreenWantsToDissmiss(_ modalScreen: ModalScreen)
}
protocol ModalScreen: UIViewController {
var delegate: (ModalScreenDelegate & ShareToFacebookDelegate)? { get set }
}
protocol ShareToFacebookDelegate: ModalScreenDelegate {
func someCustomMethod()
}
class ShareToFacebookViewController: UIViewController, ModalScreen {
weak var delegate: (ModalScreenDelegate & ShareToFacebookDelegate)?
}
or inheritance:
protocol ModalScreenDelegate: AnyObject {
func modalScreenWantsToDissmiss(_ modalScreen: ModalScreen)
}
protocol ModalScreen: ShareToFacebookDelegate where Self: UIViewController {
var delegate: ModalScreenDelegate? { get set }
}
protocol ShareToFacebookDelegate: ModalScreenDelegate {
func someCustomMethod()
}
class ShareToFacebookViewController: UIViewController, ModalScreen {
func someCustomMethod() {
}
func modalScreenWantsToDissmiss(_ modalScreen: ModalScreen) {
}
weak var delegate: ModalScreenDelegate? // **WORKS**
}

Swift 4.3 - protocols passing data between 2 files

I am struggling with understanding how protocols work. I have 2 files and want to use protocol to pass data... Here's what I am doing:
In ViewController.swift
protocol workingProtocol { func myFunc(strValue: String)}
class ViewController: UIViewController {
var interactor = workingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
In Interactor.swift
class Interactor {
func myFunc(strValue: String) {
print(strValue)
}
}
The data is not printing from Interactor.swift
Unfortunately I can't see how you inject interaction class, also your code has some problem with syntax. Here is how it should look:
protocol WorkingProtocol: AnyObject {
func myFunc(strValue: String)
}
final class ViewController: UIViewController {
var interactor: WorkingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
final class Interactor: WorkingProtocol {
func myFunc(strValue: String) {
print(strValue)
}
}
And how to use:
let interactor: WorkingProtocol = Interactor()
let vc = ViewController(interactor: interactor)
vc.doneBtn()
Protocols defines a blueprint of methods, properties and other requirements that suite a piece of functionality.
This is an example about how it works based on your code
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController {
var interactor: ProtocolName? = nil
#objc
fileprivate func doneBtn() {
interactor?.functionName(strValue: "Passing data to interactor using protocols")
}
}
class Interactor: ProtocolName {
func functionName(strValue: String) {
print("Showing value\n", strValue)
}
}
let interactor = Interactor()
let viewController = ViewController()
viewController.interactor = interactor
viewController.doneBtn()
Another example:
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController1 {
let interactor = Interactor1()
/// Init or viewDidLoad() if you're using ViewController classes.
init() {
interactor.delegate = self
}
}
extension ViewController1: ProtocolName {
func functionName(strValue: String) {
print("Printing the value: \(strValue)")
}
}
class Interactor1 {
var delegate: ProtocolName?
func someAction() {
delegate?.functionName(strValue: "Executed action in interactor.")
}
}
let vc = ViewController1()
vc.interactor.someAction()

Access a UIViewController class property without casting?

I have this function in a helper class. It's to show/hide some stuff.
func showHideDetails(controller: UIViewController, isHidden: Bool) {
...
if controller is AddNewViewController {
let addNewViewController = controller as! AddNewViewController
addNewViewController.bgButton.isHidden = isHidden
} else if controller is EditViewController {
let editViewController = controller as! EditViewController
editViewController.bgButton.isHidden = isHidden
}
...
}
Is there is a way around to have one if statement, instead of one if statement for each controller? Something like,
if controller.hasProperty(bgButton) {
controller.bgButton.isHidden = isHidden
}
Thanks
You still need to cast using as? ..., however in order not to do that for all view controllers that have the bgButton, you can define a base protocol enforcing all classes conforming to it to have the bgButton:
public protocol Buttoned {
var bgButton: UIButton { get set }
func setHideButton(_ isHidden: Bool)
}
extension Buttoned {
public func setHideButton(_ isHidden: Bool) {
bgButton.isHidden = isHidden
}
}
public class AddNewViewController: Buttoned {
#IBOutlet fileprivate weak var bgButton: UIButton!
....
}
public class EditViewController: Buttoned {
#IBOutlet fileprivate weak var bgButton: UIButton!
....
}
then you can handle the action in the actual view controller like below:
func showHideDetails(controller: UIViewController, isHidden: Bool) {
...
if let controller = controller as? Buttoned {
controller.setHideButton(isHidden)
}
...
}
In the given scenario to replace your all if with single if means that you should have a common base class or your class conform to same protocol. However type cast still requires. You can use below code to achieve your desired functionality.
Create a protocol BackgroundButton
public protocol BackgroundButton {
var bgButton: UIButton { get }
}
Conforms all your custom UIViewController with this protocol in Extension like below
extension AddNewViewController : BackgroundButton {
var bgButton : UIButton {
return yourbutton // Use any instance of UIButton from your AddNewViewController
}
}
extension EditViewController : BackgroundButton {
var bgButton : UIButton {
return yourbutton // Use any instance of UIButton from your EditViewController
}
}
Finally update your method like this
func showHideDetails(controller: UIViewController, isHidden: Bool) {
...
if let controller = controller as? BackgroundButton {
controller.bgButton.isHidden = isHidden
controller.bgButton. //Do any thing which you want with your button
}
...
}
Hope this will help you to reduce your numbers of if