I am working on a simple custom converter app.
The problem is that I have created a separate view controller for each (!) conversion (fahrenheit to celsius, ounces to kilograms and so on) and I would like to know how I could re-use only one converter view controller and just change the calculations done in the background?
This is what I have done so far:
mainVC (contains buttons for each converter viewController)
(right now 10) converter VCs for each conversion the user can do
MathLib.swift with different functions for each math formula
Each converter VC contains a name label (eg. ounce to kilogram), a number pad and an input/output label. When the user touches a number the conversion is done on the fly with the right math formula from MathLib.swift. I just use the corresponding formula with a return. This is an example of a formula:
static func stoneToKilo(stone: Double) -> Double {
let kilo = stone / 0.15747
return kilo
}
Now what I want to do is just having one(!) converter VC and depending on the button pressed in mainVC the right formula is used in MathLib and the name of the label is changed to the right conversion type.
All buttons should point to just one VC. Now when a button is pressed and the view controller is presented how can I check from which button the user comes? For example the button name or tag?
When I know the button name or tag how can I use this to use the right formula in MathLib.swift or change the name label? Do I have to use a switch case? If so: how can I set up the switch case to check for the button used and then point to the right math function and change the name label?
In short: Using just one VC, checking for which button brought the user to this VC and then using something (switch case?) to change some things like labels on the VC and use the right function in a library.swift file.
This is quite difficult for me and if you could help me I'd appreciate this.
You are on the right track.
Now that you can get which button was clicked, like you said by name or the tag (i prefer the second, think of when you'll be making your app available in different languages, i.e localization).
Your main view controller should now only contain an input, and a button, and may be a title for the screen, to know which conversion is being used.
As for the calculation (the business logic), i would recommend that you define a protocol that has one method called convert that takes one argument, being the value the user would want to convert.
protocol Converter {
func convert( _ value : Double) -> Double
}
and then create as much classes as there are conversions to be made, let me take only one example and keep it as simple as possible, consider the converter meters to kilometers.
let's call it DistanceConverter that should implement the Converter protocol
class DistanceConverter : Converter {
func convert( _ value : Double) -> Double {
return value / 1000.0
}
}
Now when you tap the button from the first screen called meter to kilometers, in your handler you would :
1) instantiate an instance of the DistanceConverter.
2) instatiante an instance of MainViewController. and give it the DistanceConverter instance you created in step 1.
3) keep a reference of the DistanceConverter as an iVar in your mainViewController.
the key point here is to keep it in an iVar of type Converter, so it can hold all the instances of any class that would implement the Converter protocol you will be creating.
4) in the handler of the button 'convert' of the mainViewController you call the method convert on the mainViewController 's iVar that you made in step 3.
And so now in order for you to create another converter, let's say TemperatureConverter that handles Celcius to Fahrenheit, you create a new class :
class TemperatureConverter : Converter {
func convert( _ value : Double) -> Double {
// return the calculation,
}
}
When you tap the button temerature on the first screen:
1) instantiate an instance of the DistanceConverter.
and then keep repeat all the remaining steps as before (you can easily refactore all the rest of the steps).
This is the Strategy pattern out of the box, so i recommend you read about it and it's application with swift.
You should pass a delegate to the view controller where the conversion happens, which implements the function you want to use. When you instantiate the view controller before you push or present it, inject the delegate.
Define a protocol with a protocol-method for each method you want to use and then let the delegate implement them via an extensions. The delegate can be even be the ViewController that allows to select a conversion type.
You can then define an enum with your conversion type and also pass it to the new view controller. On your view controller just switch over the type and run the correct delegate method.
enum ConversionType: Int {
case fahrenheitToCelsius
// other cases
}
protocol ConversionDelegate {
func convertToCelsius(fromFahrenheitDegrees fDegrees: Double) → Double
// other protocol functions
}
// Other protocols here
class SelectionViewController: UIViewController {
// your usual stuff like viewDidLoad
func presentConversionViewController(forConversion type: ConversionType) {
let destinationVC = ... // instantiate your VC from storyboard here
destinationVC.delegate = self
destinationVC.conversionType = type
// present/push your VC
}
extension SelectionViewController: ConversionDelegate {
func convertToCelsius(fromFahrenheitDegrees fDegrees: Double) → Double {
// you Math.lib func here
return (fDegrees - 32) / 1.8 // example
}
// implement the other functions
}
On your destination view controller:
class DestinationViewController: UIViewController {
var delegate: ConversionDelegate!
var type: ConversionType!
// your usual stuff like viewDidLoad
#IBAction func calculateButtonPressed(_ sender: UIButton) {
switch type {
case .fahrenheitToCelsius:
// read the input value from somewhere like a UITextField
// most likely you will have to convert a String to Double in the example
let result = delegate.convertToCelsius(fromFahrenheitDegrees: yourInputValue)
// output the result to some label or whatever you like
}
}
}
Related
I got stuck because one ViewController trying to pass data from one ViewController to another. To explain my code is that I have label in mainViewController named name1 with data inside of it and I am trying to pass that data that is in text format to the name2 label in the firstViewController of name2 label, Can you please help me with it.
Thank you in advance
class mainViewController: UIViewController {
//its inside a segment controller
#objc func Seg(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
let firstViewController = SharedViewController()
print(self.name1.text!)
firstViewController.name2.text = self.name1.text!
self.addChild(firstViewController)
self.bottomContainer.addSubview(firstViewController.view)
firstViewController.didMove(toParent: self)
default:
let secondViewController = Shared2ViewController()
self.addChild(secondViewController)
self.bottomContainer.addSubview(secondViewController.view)
secondViewController.didMove(toParent: self)
}
Don't try to manipulate another view controller's views directly. That violates the principle of encapsulation and often doesn't work (as in this case.)
You should add a string property to the other view controller, and install that string into the label text in viewWillAppear.
Also note that creating a view controller with an init like SharedViewController() doesn't usually do what you want. (You won't get the view controller's views loaded from it's storyboard/xib.) You usually want to use the Storyboard instantiate method, or to load it from the XIB.
I am a newbie to iOS app development trying to build a tip calculator. The basic functioning of the app is completed. My Mainstoryboard has a segment control which shows three % values such as 10, 20, 30. There is a Settings button which on click takes me to a new page and shows the similar % values in a segment control.
What I want to do is that, when a number is clicked (a segment) it should be saved as the default tip% value. How should I pass this value to the Mainstoryboard function where I have written a function to calculate the tip amount?
I suppose you are using a segue for moving to the other view, so use prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "tipSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.tipPercent = someValue
}
}
}
You can write a protocol for setting the default percentage, and have your MainViewController conform to that protocol. When a user taps a button in your SettingsViewController you can call a function (defined within the protocol) to let the "delegate" (MainViewController) know what has happened, and set your default variable accordingly. If this is a permanent setting it might be better to use UserDefaults, as this is exactly what that was designed for. Then, upon loading MainViewController you can access that UserDefaults variable, if it exists.
A little protocol help:
Your protocol could be defined very simply - something like this (and this has to be declared outside of your viewControllers - I often do it above the class declaration of a related viewController):
protocol SettingsDelegate {
func didUpdateDefaultTipPercent(to percent: Float)
}
That's it for the protocol declaration, unless you anticipate needing other functions. Note that you don't actually define the functionality of that function - each viewController that conforms to the protocol may have a different definition of that.
Then, in your settingsViewController you might have an optional variable for protocol conformer like this:
weak var settingsDelegate: SettingsDelegate?
and after the user chooses a default percentage you can safely check to see if the delegate exists and pass that new number to the delegate like so:
if let delegate = self.settingsDelegate {
delegate.didUpdateDefaultTipPercent(to: 15.0) //this "15.0" will come from your segmented control action or whatever
}
In your MainViewController, in your prepareFor(segue... you will need to check to see if you are going to settings, and set Main as the delegate:
...
if let settings = segue.destination as? SettingsViewController {
settings.settingsDelegate = self
}
And, finally, you'll need to make your MainViewController conform to the protocol. I usually do this as an extension just to make it easier to find, and to keep it separated from other things:
extension MainViewController: SettingsDelegate {
func didUpdateDefaultTipPercent(to percent: Float) {
self.defaultPercentage = percent
}
}
In a couple of my projects I think I'm not created a great structure in many cases.
It could be a game where I've created a game board (think about chess) with a grid of 8 * 8 cells. Each cell has a gesture recognizer that relies on a subclass (cell.swift), with the game logic in a parent ViewController.
For arguments sake, let us say we want to display to the user which square they have touched.
I've found out how to do this from the subclassed UIView (obvs. create the alert in the subclassed UIView / cell.swift in this example)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
but it seems to break the structure of the app - but wouldn't it be the same accessing an action in the parent ViewController? What is the best way of approaching this>
Your rootViewController is the VC on the bottom of your stack. It's not a safe way to access the visible VC, and is rarely useful, in general (there are cases, but I doubt your app would find them useful).
What you likely want to use is a delegate pattern. Let's say the parent VC that displays your chess board (let's call this MyBoardViewController), conforms to a protocol like the following. MyView is whatever custom UIView class you're using for the chess squares:
protocol SquareAlertHandler {
func handleSquarePressed(sender : myView)
}
And add the following property to your MyView class:
weak var delegate : SquareAlertHandler?
And replace whatever event handler you're currently using, with the following (I'm assuming you're using a UIButton in IB to handle the press, and have arbitrarily named the outlet 'didPress:'):
#IBAction didPress(sender : UIButton) {
delegate?.handleSquarePressed(self)
}
Now, add the protocol to your MyBoardViewController, and define the method:
class MyBoardViewController : UIViewController, SquareAlertHandler {
... ... ...
func handleSquarePressed(sender : myView) {
// Do something to handle the press, here, like alert the user
}
... ... ...
}
And finally, wherever you create the MyView instances, assign the MyBoardViewController instance as the delegate, and you're good to go.
Depending on your Swift literacy, this may be confusing. Adding code, so that I can at least match up the class names, would help to clarify things.
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I know that you can pass information between two view controllers if they are connected by a segue using
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let destinationViewController = segue.destinationViewController as? searchTermViewController else { return }
destinationViewController.courseCodes = selectedCourses
}
}
The above code gives an error if there is no segue because of the .destinationViewController. How do i pass information between to arbitrary view controllers without having to set up a global variable?
You can set up a delegate pattern in order to do this.
Here are the steps for setting up the delegate pattern between two objects, where object A is the delegate for object B, and object B will send messages back to A. The steps are:
Define a delegate protocol for object B.
Give object B an optional delegate variable. This variable should be weak.
Make object B send messages to its delegate when something interesting happens, such as when it needs a piece of information. You write delegate?.methodName(self, . . .)
Make object A conform to the delegate protocol. It should put the name of the protocol in its class line and implement the methods from the protocol.
Tell object B that object A is now its delegate.
Here is a tutorial to give you a working example https://www.youtube.com/watch?v=9LHDsSWc680
Go to your storyboard, select the second view controller, go to the Identity inspector tab and give a StoryBoard ID value. This should be a unique value to identify your view controller.
Now in your first view controller', you can run this code. This will basically create an object of the second view controller, set the property value (for transferring data) and push it (same as the segue does)
let ctrl = self.storyboard?.instantiateViewControllerWithIdentifier("detailsView")
as? SecondViewController
ctrl?.userId = 250 // data to pass.
self.navigationController?.pushViewController(ctrl!, animated: true)
provided userId is a variable in your SecondViewController class. Replace
detailsView with the storyboard id value you gave earlier.
class SecondViewController: UIViewController {
var userId : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// do something with self.userId
}
}
I'm trying to pass few variables via a segue. Initially I capture 6 variables on the first screen which I would like to pass on to the second view controller.
Each variable is captured through a text box capturing an integer and I called them T1, T2, T3 ... T6. At present I refer to the value through T1.text.toInt()!. Before I pass these values via segue, should I first create a variable like var T1 = T1.text.toInt()! ?
What is the best way of designing this?
inside prepareForSegue you have access to the UIController instance that will open:
class FirstPageUIViewController:UIViewController {
...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var controller = (segue.destinationViewController as! MyNextViewController)
controller.T1 = "value to pass"
}
}
This means you define you variables in MyNextViewController (the controller for your second screen) and the variables are already set when your MyNextViewController instance takes over control.
You also might decide to name your variables starting with small letters according to the swift style guide.