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
}
}
Related
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
}
}
}
So here is my first view controller class:
import UIKit
class AboutUsTableViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let volunteerPageVC = segue.destination as! DedicatedVolunteerViewController
// Idk why I have to use 'as!' instead of 'as'. Xcode made me do it
volunteerPageVC.person = "John Smith"
}
}
Here is my second view controller class:
import IUKit
class DedicatedVolunteerViewController: UIViewController {
var person: String?
#IBOutlet weak var HeaderTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
HeaderTitle.text = "About " + person!;
}
}
On my Main Storyboard, each of the cells on the AboutUsTableView segue to the DedicatedVolunteerViewController. I want to ability to have one view controller that can be used multiple times (i.e. people's profiles).
When I click on any of the cells in the table view, I get these errors:
2016-12-26 11:44:27.075 MyApp iOS[8350:493673] Unknown class _TtC20MyApp_iOS31DedicateVolunteerViewController in Interface Builder file.
2016-12-26 11:44:29.698 MyApp iOS[8350:493673] Unknown class _TtC20MyApp_iOS31DedicateVolunteerViewController in Interface Builder file.
I'm confused since when I compile the app, it gives no error saying about "Unknown class" or an error saying I need to add/import DedicatedVolunteerViewController into AboutUsTableViewController to be able to use it. I even tried to import it, but Xcode wouldn't let me and kept giving me errors.
P.S. I've a lot of trouble considering most places online give documentation on older versions of swift or obj-c which make it hard to find out how to use new versions of code.
It sounds like the class name is wrong in your storyboard scenes. Go follow the segue(s) from your table view controller to your second view controller. Then select the scene for the destination view controller, select the view controller itself, and select the "identify inspector." Check the class of the destination view controller. From the error you're getting, it sounds like it's class is _TtC20MyApp_iOS31DedicateVolunteerViewController instead of DedicateVolunteerViewController.
Swift uses 'optionals' which essentially means a nullable object. Optionals can have a valid value or nil. It is recommended that you use the 'guard' statement to handle optionals.
So, your prepareForSegue should look like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let volunteerPageVC = segue.destination as? DedicatedVolunteerViewController else {
//Handle the nil value, log, etc
return
}
volunteerPageVC.person = "John Smith"
}
The ! is explicitly unwrapping the optional - meaning it MUST have a value or the app will crash. You should avoid using them where possible and use the ? as I show above instead with a guard statement. In the code above, it attempts to cast the segue.destination to a DedicatedVolunteerViewController but if it can't, it will fall into the guard statement (meaning the result was nil).
There's a lot of good information about optionals on Apple's site. Check out: Swift Basics
As for your specific error, it looks like a simple mis-spelling. The error is referring to 'DedicateVolunteerViewController' (missing a 'd') and your class name is 'DedicatedVolunteerViewController'
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 make an app that uses three containers to show different content, but I'm having trouble communicating between the containers. I succeeded to use a segue to send some information at the tap of a button in one container to another container, but part of this information also has to be relayed to the third container. For this I wanted to use a delegate, but I cannot reference the right ViewController to the delegate variable.
So what I want goes as follows:
CollectionViewCell tapped, triggering segue to TableVC
TableVC receives information and updates the table
TableVC triggers delegate function in third VC
Third VC takes in some info and updates view
In the above I have managed to get 1 and 2 to work, but got stuck at 3.
I have made my protocol as follows:
protocol PurchaseDelegate {
func addToTotalAmount(product : Product)
}
In the TableVC I have declared var delegate : PurchaseDelegate? = nil and in the IBAction triggered from the segue: delegate?.addToTotalAmount(product)
In the third VC I have implemented the delegate as follows:
class thirdVC:UIViewController,PurchaseDelegate {
func addToTotalAmount(product : Product) {
println("Adding....")
}
}
All three containers are within a main VC that does some initial stuff in the application.
My problem is, that I don't know how to get a reference from thirdVC to my delegate variable in my tableVC.
Thanks in advance.
I ended up finding the solution to the problem after a bit further searching with inspiration from #Anna Dickinson.
Firstly, the containers must be ordered correctly in the storyboard. The container whose view controller implements the delegate protocol must be first in the list and then the other view controller further down.
Then, in the main view controller - the view controller for the view with the containers - the prepareForSegue function is implemented, since it will be triggered as the containers are initialized.
This all of the code remains as above, but the main view controller will be something like the following:
class MainViewController: UIViewController {
var actionVC : FirstViewController! // This is the one, that implements the delegate protocol
var tableVC : SecondViewController! // This is the one, that has a delegate variable
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "firstVC"){
self.actionVC = segue.destinationViewController as FirstViewController
} else if(segue.identifier == "secondVC"){
self.tableVC = segue.destinationViewController as SecondViewController
self.tableVC.delegate = self.actionVC
}
}
}
I'm not sure if the is the right, nor the best way to do this, but it works perfectly for what I need.
I'm trying to pass data from the modal ViewController to his source ViewController. I think I have to use delegation but it doesn't work.
protocol communicationControllerCamera{
func backFromCamera()
}
class Camera: UIViewController{
var delegate: communicationControllerCamera
init(){
self.delegate.backFromCamera()
}
}
class SceneBuilder: UIViewController, communicationControllerCamera{
func backFromCamera(){ // Never called
println("YEAHH")
}
}
The backFromCamera method it's not called. What did I do wrong?
You didn't set a delegate so it was empty when you tried to call backFromCamera().
Here's a simple working example you can test out. Notice the use of the optional type (?) for the delegate.
// Camera class
protocol communicationControllerCamera {
func backFromCamera()
}
class Camera: UIViewController {
var delegate: communicationControllerCamera? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.backFromCamera()
}
}
// SceneBuilder class
class SceneBuilder: UIViewController, communicationControllerCamera {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var myCamera = Camera()
myCamera.delegate = self
self.presentModalViewController(myCamera, animated: true)
}
func backFromCamera() {
println("Back from camera")
}
}
You can find all the information you need in Apple's Swift documentation.
Obviously the chosen answer is correct, but it didn't help me. I did successfully implement protocols though, so I wanted to provide my own explanation in case anyone is struggling with grasping the concept, like I was.
Protocol Code Is Written in Three Places:
Two ViewController Classes
The Protocol itself (code written outside of VC classes)
When I write my protocols, I put them in my "ToolBox" document and I still write comments to remind myself which VCs are doing what. Two examples:
So there is always:
The protocol code (shown above)
Code in a VC which initiates the action
Code in a VC which is delegated to carry out the action
1. The protocol code
See the image above for a reference. Essentially, the protocol code is just where you give the protocol a name and declare what functions you want to remotely call/delegate to. Name the protocol. Declare the names of the functions that can be called upon and declare their parameter types such as string, etc.
2. Code in a VC which initiates the action
This is the code that initiates the protocol. In this example, this is code from a table cell, which needs to delegate some work back to the main table VC. The first screenshot shows the creation of the delegate variable and the second screenshot is the actual use of that variable.
So the below code are table-cell buttons. They all need to trigger code outside of the cell VC, so they all trigger functions using the protocol I declared above.
3. Code in a VC which is delegated to carry out the action
Now the protocol is being called, but which VC answers the call? To answer that question, choose the VC and add the protocol name to the class declaration:
Lastly, you need the actual meat of the whole thing. Not the trigger, not the protocol itself, not the class declaration... but the actual function you want to call:
Hope This Helps
I don't know why protocols just wouldn't sink through my thick skull but they wouldn't. I hope this helps others like me!