How to call method from another class in Swift 4.0 - ios

I am working an an app that involves a wheel. I want to execute a function in another class when the angular velocity reaches 0 after being spun.
The setup is that I have a SKView embedded in a view controller. I have a class called WheelView that is tied to that SKView.
Full class: https://github.com/swiftishard/wheelpractice2/blob/master/WheelView.swift
This is the relevant portion of code.
class WheelDelegate: NSObject, SKSceneDelegate {
func didSimulatePhysics(for scene: SKScene) {
guard let wheelScene = scene as? WheelScene else { return }
if (wheelScene.wheel.physicsBody?.angularVelocity ?? 0.0) > 0.0 {
print(wheelScene.wheel.physicsBody?.angularVelocity)
if(wheelScene.wheel.physicsBody?.angularVelocity ?? 0.0) < 25.0 {
wheelScene.wheel.physicsBody?.angularVelocity = 0.0
if(wheelScene.wheel.physicsBody?.angularVelocity ?? 0.0) == 0.0 {
print("CALL METHOD FROM ANOTHER VIEW CONTROLLER")
}
}
}
}
}
Main view controller where method to call is located
import UIKit
class Main: UIViewController {
func methodToCall() {
print("METHOD CALLED")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
So how would I call methodToCall from the didSimulatePhysics code?

I'm assuming WheelDelegate is created within Main as an object.
Multiple ways you can do this:
1) Most common way I have seen is to create a protocol as well as some delegates.
Inside Main class, right below all the imports, you can do.
protocol ViewControllerTalkingDelegate {
func methodToCall()
}
Inside WheelDelegate you add this as a global variable
var delegate:ViewControllerTalkingDelegate?
Then, whenever you create WheelDelegate inside Main
let wheelDelegate = WheelDelegate()
wheelDelegate.delegate = self
Then, inside Main at the bottom, you can do
extension Main:ViewControllerTalkingDelegate {
func methodToCall() {
//Do Something
}
}
Now, inside WheelDelegate you can do delegate.methodToCall().
2) Other way I have seen is to pass the 1st class as a variable to the 2nd class.
Inside WheelDelegate add a global variable
var myViewController:Main?
You can then either take in Main as a parameter when initializing or when you create WheelDelegate you can do
var wheelDelegate = WheelDelegate()
wheelDelegate.myViewController = self
Then inside WheelDelegate you can do
self.myViewController.methodToCall()

Related

MVVM + RXSwift+ Coordinator how to set data?

Good night!
Can you tell me how can I write data from controller 2 to controller 1?
I have a coordinate at the main screen.
final class MenuCoffeLikeCoordinator: TabBarPresentableCoordinator {
var tabBarItem: UITabBarItem = {
let title = "Меню"
let image = UIImage(asset: Resources.Assets.TabBarItems.mainTabBar)
let selectedImage = UIImage(asset: Resources.Assets.TabBarItems.mainTabBarSelected)
let item = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
return item
}()
var navigationController: UINavigationController
init(navigationController: UINavigationController = UINavigationController()) {
self.navigationController = navigationController
}
var didFinish: (() -> Void)?
func start() {
self.navigationController.pushViewController(createMenuCoffeLikeFlow(), animated: true)
}
func stop() {}
func createMenuCoffeLikeFlow() { -> UIViewController {
let menuController = MenuCoffeLikeAssembler.createModule()
menuController.rx.didTapMapLayer.onNext {
let controller = self.createCoffeeBarMap()
self.navigationController.pushViewController(controller, animated: true)
}
return menuController
}
private func createCoffeeBarMap() -> UIViewController {
let controller = CoffeeBarContainerAssembler.createModule()
controller.obsRelay.subscribe(onNext: { event in
self.navigationController.popViewController(animated: true)
})
return controller
}
}
In the createMenuCoffeLikeFlow function, I create the main screen, and when I click on the button, I go to screen 2 (createCoffeeBarMap)
Inside the function (createCoffeeBarMap), I subscribe to the PublishSubject, and when the data changes, I get a new text.
I need to write this text in the menuCoffeeControler which is in the createMenuCoffeLikeFlow function. How can i do this?
Here's how I would implement it using my Cause Logic Effect (CLE) architecture. With CLE you don't need to implement a Coordinator because a reusable Coordinator class already exists in the library. This means less code for you to write.
Unlike yours, this sample is complete and will compile. The only thing missing is the creation and layout of the views inside the view controllers.
import Cause_Logic_Effect
import RxCocoa
import RxSwift
import UIKit
/// This function produces the view controller that is attached to your tab bar controller. I don't put the
/// `UITabBarItem` in here. Instead I attach that when connecting to the tab bar controller.
func menuCoffeLikeTab() -> UIViewController {
// the `configure` function calls its closure inside the viewDidLoad method.
let menuController = MenuController().configure { $0.connect() }
let controller = UINavigationController(rootViewController: menuController)
return controller
}
/// It depends on how you want to layout your view controllers on whether anything else goes in here. If you
/// use storyboards, then add `#IBOutlet` before the views here. If you create your views programatically
/// then add a `loadView()` override.
final class MenuController: UIViewController {
var mapLayerButton: UIButton!
var textField: UITextField!
let disposeBag = DisposeBag()
}
extension MenuController {
func connect() {
// This is the meat. The `coffeeBarResponse` observable pushes the
// CoffeeBarController onto the navigation stack when approprate and
// then emits any values produced by it. Notice how this looks alot like
// a network call except you are querying the user instead of the server.
let coffeeBarResponse = mapLayerButton.rx.tap
.flatMapFirst(pushScene(on: navigationController!, animated: true) {
CoffeeBarController().scene { $0.connect() }
})
.share()
// The pushScene function above will create a coordinator for the
// CoffeeBarController. When needed, the coordinator will create the
// view controller, call its `connect` and emit any values from that.
// When the Observable completes, the coordinator will pop the view
// controller off.
coffeeBarResponse
.bind(to: textField.rx.text)
.disposed(by: disposeBag)
}
}
final class CoffeeBarController: UIViewController {
var saveButton: UIButton!
var textField: UITextField!
}
extension CoffeeBarController {
func connect() -> Observable<String> {
// when the user taps the save button, this will emit whatever value is
// in the text field and then complete the observable.
saveButton.rx.tap
.withLatestFrom(textField.rx.text.orEmpty)
.take(1)
}
}
Like I said above, this uses a reusable Coordinator class that is part of the library instead of you having to write your own all the time. This architecture will significantly reduce the amount of boilerplate code you have to write. Learn more at https://github.com/danielt1263/CLE-Architecture-Tools and join the RxSwift Slack to learn more about RxSwift in general.
this is a typical scenario where DI comes to rescue. You have to have some kind of a shared container which will register and resolve dependencies. I use Dip https://github.com/AliSoftware/Dip.git and here is an example with your code. The idea is the following - you register closure in one VC and pass it to another.

Instantiate the delegate pattern one time or more in Swift?

I am studying delegate pattern for swift on below code. I can not be pretty sure how can I use this option "without the need to reinstantiate the view.
protocol ShapeViewDelegate {
func drawShapeView(_ shapeView: ShapeView)
}
class ShapeView: UIView {
var strokeColor: UIColor?
var fillColor: UIColor?
var delegate: ShapeViewDelegate? {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
delegate?.drawShapeView(self) // self means object( ShapeView() ) is it instantiated ?
}
}
View object supposed to ready coming from delegate object but I didn't instantiate it, where this object instantiated using protocol automatically instantiated it at the run time. So I am writing an example like this :
class ShapeViewController: ShapeViewDelegate {
drawShapeView(view)
}
View is instantiated in other words occupied the memory at this example ?
You have to define drawShapeView() in ShapeViewController. In your code you are using or calling drawShapeView() which is already being called in your ShapeView. And about instantiate, yes you need to instantiate ShapeView in ShapeViewController or any other place you are confirming the delegate.
Code in your ShapeViewController -
class ShapeViewController: ShapeViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let shape = ShapeView(...)
shape.delegate = self
view.addSubView(shape)
}
func drawShapeView(_ shapeView: ShapeView) {
//your code here
}
}
Via this feature, you can have many definitions of ShapeView based on different ShapeViewController instances without worrying to modify ShapeView.

how to force run a method in all viewControllers viewDidLayoutSubviews functions automatically?

say I have this extension that helps me in changing the font size in all text elements of a UIViewController
extension UIView {
func changeFontSize(){
let fontSize = CGFloat(5)
if let v = self as? UIButton {
v.titleLabel?.font = v.titleLabel?.font.withSize(fontSize)
print("didChangeFontSizeFor_Button")
} else if let v = self as? UILabel {
v.font = v.font.withSize(fontSize)
} else if let v = self as? UITextField {
v.font = v.font?.withSize(fontSize)
} else {
for v in subviews {
v.changeFontSize()
}
}
}
}
it works fine when I call it like this
override func viewDidLayoutSubviews() {
view.changeFontSize()
}
now the question, is there a way where I can make this more dynamic to be forced in all viewControllers?
say we have 3 view Controllers, and I want to make some CocoaPods library where people just make a simple call like this
forceAppFontSize.fontSize = CGFloat(15)
to change the font size for all other screens..
class 1
import UIKit
class v1: UIViewController{
override func viewDidLayoutSubviews() {
print("someCommand1")
print("someCommand2")
}
}
class 2
import UIKit
class v2: UIViewController{
override func viewDidLayoutSubviews() {
print("someCommand")
}
}
class 3
import UIKit
class v3: UIViewController{
}
is there a way to make this dynamic without breaking the original viewDidLayoutSubviews ? see class 1 for example, the view has some commands already that to be not destroyed or replaced.
There is a much simpler way. Your actual goal is to update all views in your app. There is no need to go through each view controller. Simply call your changeFontSize() extension method on your app's main window.

How do I pass data between two scenes in Swift?

I have two scenes where I would like to pass a single variable to another scene using a segue. I have tried but unfortunately all tutorials I have seen are dealing with the storyboard. I am not using the storyboard. I am doing all of this programatically.
Here is the segue i am trying to initialize:
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueTest") {
var lose = segue.destinationViewController as! loserScene;
lose.toPass = scoreLabelNode.text
}
}
The Loser Scene is my second scene.
scoreLabelNode.text is the text of an NSTimer which i'm using as a score.
I want to move the scoreLabelNode.text into another scene which is my loserScene.
My loserScene is set up like this:
import SpriteKit
import Foundation
let GameOverLabelCategoryName = "gameOverLabel"
class loserScene: SKScene {
var toPass: String!
var scoreLabel = SKLabelNode(fontNamed:"[z] Arista Light")
override func didMoveToView(view: SKView) {
var gameOver = SKSpriteNode(imageNamed: "gameover.png")
gameOver.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
self.addChild(gameOver)
println(toPass)
}
}
I am trying to print 'toPass' to test the string segue, but I just get nil.
OK! So instead of using a segue. WHICH YOU CANNOT DO WITH AN SKSCENE. I used a struct. I put the struct outside of one of my scenes that I wanted data to be passed from, and made sure the data was being fed into the struct. I then accessed the struct from another scene and it works perfectly!
struct Variables {
static var aVariable = 0
}
this is the struct^ I have it set to 0 but it will be updated automatically when the score is recorded at the end of the run of my game.
I then accessed it like so in another scene:
print(Variables.aVariable)
This prints the variable from the struct in my new scene. I can also turn this struct into a string and use it for a labelnode.
Your toPass String returns nil because it is not initialized when you send the value from your prepareForSegue method.
Create a some function for example;
// Do whatever you need with the toPass variable.
func printToPassValue(){
print(toPass)
}
And add the property observers to your toPass variable. like this;
var toPass: String? {
willSet {
printToPassValue() // Call your function
}
}
And add this function to your viewDidLoad method.
override func viewDidLoad() {
super.viewDidLoad()
printToPassValue()
}

Delegate loosing View Controller objects swift

All,
I have set up a protocol, and the view controller is the delegate of this protocol, like so :
import Foundation
protocol PayButtonProtocol {
func enablePayButton()
func disablePayButton()
}
And the view controller is the delegate :
class ViewController: UIViewController, PayButtonProtocol
The protocol functions are as follows :
func enablePayButton() {
println("Button enabled")
PAYBarButton.enabled = false
}
func disablePayButton() {
PAYBarButton.enabled = false
}
I set a class and assign the delegate :
class Trigger
{
var delegate:PayButtonProtocol?
func EnablePayButton()
{
delegate?.enablePayButton()
}
}
Then I set the trigger to run the function :
let localtrigger = Trigger()
localtrigger.delegate = ViewController()
localtrigger.EnablePayButton()
This works and the 'button enabled' is displayed in the console. But the Bar Button (PAYBarButton) is nil and it seems that the view controller has lost its hieracy as I cannot access any of the view controllers objects. The View Controller was built with interface builder. Anyone got any ideas ? Is it
localtrigger.delegate = ViewController()
that rebuilds the viewconotroller and makes the original one not accessible ? If so how do i do this ?
if you are creating the localTrigger object inside your ViewController class you can just do:
let localtrigger = Trigger()
localtrigger.delegate = self // self is an instance of ViewController
localtrigger.EnablePayButton()

Resources