Storyboard: Connect IBOutlet to a ViewModel/Model bypassing the ViewController - ios

I have a rather complex form designed in the Interface Builder, having about 20 IBOutlets. The form is split onto multiple sections and is static.
Some of the sections might be enabled while the others are disabled (hidden). After the form has been filled in, the app needs to read all of the values (i.e. IBOutlets such as UITextField) and send them to the server.
I used multiple UIStackViews to design each section of the form, so that they could be easily switched on or off.
It's logical after having such a separation with views to have a model that would reflect the same order.
However, I had to link all of the IBOutlets to the UIViewController subclass, flattening any hierarchy.
What I'm trying to achieve is to link individual Form Section Model with specific View. The controller will be responsible only for enabling/disabling the section. The Form Section Model would actually enable specific labels and StackViews and fill in the form values.
Here is the example code I'd like the interface to look like:
import UIKit
class AddressSection {
#IBOutlet weak var sectionStackView: UIStackView!
#IBOutlet weak var sectionTitleLabel: UILabel!
#IBOutlet weak var addressTextField: UITextField!
#IBOutlet weak var isPrimary: UISwitch!
var isHidden: Bool {
get {
return sectionStackView.isHidden
}
set(newValue) {
sectionStackView.isHidden = newValue
}
}
init(){}
}
class NameSection {
#IBOutlet weak var sectionStackView: UIStackView!
#IBOutlet weak var name: UITextField!
#IBOutlet weak var surname: UITextField!
var isHidden: Bool {
get {
return sectionStackView.isHidden
}
set(newValue) {
sectionStackView.isHidden = newValue
}
}
init(){}
}
class MyViewController: UIViewController {
let name = NameSection()
let address = AddressSection()
override func viewDidLoad() {
super.viewDidLoad()
name.isHidden = false
address.isHidden = true
}
}

Related

How can I access data from a container view from the parent view controller in swift?

I have a view controller(SignUpController) and it has 1 image view at the top, 2 buttons at the bottom and 1 view as container view. The Container view(InfoRegisterController) contains scrollview with several text fields. Container view doesn’t contain any buttons. So, now i need to access text fields of Container view in the parent view controller (or can say as : I need to access all data from container view to parent view controller) so that I can register the user by clicking Register button at the bottom in SignUpController. I couldn’t solve it through delegates and NSUserDefaults too. So , Please help me to solve this in swift 4 ?
This is the parent view controller:
import UIKit
import Material
class SignUpController: UIViewController {
#IBOutlet weak var backgroundView: UIView!
#IBOutlet weak var register: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
backgroundView.layer.borderWidth = 1
backgroundView.layer.borderColor = UIColor.blue.cgColor
register.layer.cornerRadius = 5
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) // No need for semicolon
let tv: InfoRegisterController = self.childViewControllers[0] as!InfoRegisterController
print("Hello")
//tv.viewWillAppear(true)
}
All data must be accessed to this place so that I can post it to server.
#IBAction func Register(_ sender: Any) {
}
} // class end
Container View:
import UIKit
import Material
class InfoRegisterController: UIViewController, UITextFieldDelegate, UITableViewDelegate,UITableViewDataSource,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
#IBOutlet weak var fullName: TextField!
#IBOutlet weak var Email: ErrorTextField!
#IBOutlet weak var Password: TextField!
#IBOutlet weak var verifyPassword: TextField!
#IBOutlet weak var address: TextField!
#IBOutlet weak var permanentAddress: TextField!
#IBOutlet weak var currentAddress: TextField!
#IBOutlet weak var DOB: TextField!
#IBOutlet weak var occupation: TextField!
#IBOutlet weak var selectGender: TextField!
#IBOutlet weak var identificationType: TextField!
#IBOutlet weak var identificationID: TextField!
#IBOutlet weak var issuedDate: TextField!
#IBOutlet weak var issuedDistrict: TextField!
#IBOutlet weak var licenseNumber: TextField!
#IBOutlet weak var fathersName: TextField!
#IBOutlet weak var grandfathersName: TextField!
#IBOutlet weak var mobileNumber: TextField!
override func viewDidLoad() {
super.viewDidLoad()
self.fullName.delegate = self
self.Email.delegate = self
self.Password.delegate = self
self.verifyPassword.delegate = self
self.address.delegate = self
self.permanentAddress.delegate = self
self.currentAddress.delegate = self
self.DOB.delegate = self
self.occupation.delegate = self
self.identificationID.delegate = self
self.issuedDate.delegate = self
self.issuedDistrict.delegate = self
self.licenseNumber.delegate = self
self.fathersName.delegate = self
self.grandfathersName.delegate = self
self.mobileNumber.delegate = self
}
}
Here, I need to access all fields fullName,Email, Password,verifyPassword,address, permanentAddress,currentAddress, DOB,occupation and all others to SignUpController so that I can register new user.
To gain access to the container view from your parent, you will have to pass a reference from the container view to the parent and then use it in the parent view controller, there are many ways to do that and here is one of them.
In your viewDidAppear of InfoRegisterController which is the container view controller add the following code, this method will get a reference of InfoRegisterController into the parent to be used.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let signUpControllerParent = self.parent as! SignUpController
signUpControllerParent.saveContrainerViewRefference(vc: self)
}
Now in SignUpController add a local variable for the coming reference to be saved and used later to get the data from the textfields.
var infoRegisterRefferenceVC : InfoRegisterController?
Add this method also in your parent SignUpController
func saveContrainerViewRefference(vc:InfoRegisterController){
self.infoRegisterRefferenceVC = vc
}
Now you can have access to all the textfields and the methods in the container view from the parent for example:
var fullNameTextField = self.infoRegisterRefferenceVC.fullName.text
This should be it :)
There are several ways to accomplish this. You can use "Delegate" and "Protocol" or prepareForSegue method in parent view controller.
var somePropertyYouWantToAccess: NSString?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yourChildViewSegueIdentifier" {
let childVc = segue.destinationViewController as ContainerViewController
self.somePropertyYouWantToAccess = childVc.firstName
}
}

Distinguish which button was clicked in custom UIView

I have a custom class which inherits from UIView inside of which is a UIImageView and button. I'm adding them to my storyboard as UIView and let's say there are two views. I have a delegate and a function which executes when the button is pressed and it works for both UIViews.
My problem is that I don't know how to tell which one was clicked. I need to pass it to another VC and then display suitable image in the UIView which was clicked. I've been thinking of setting ids or something but it does not seem like a very scalable idea.
And I cannot simply add #IBAction because the view is UIView as a whole.
Inside the ViewController I've added them as #IBOutlets:
#IBOutlet weak var view: CustomImageView!
#IBOutlet weak var view2: CustomImageView!
EDIT:
I'm pretty close to creating reusable and scalable solution - I'm just assigning the tag to every object and than passing it and retrieving it. Problem I've encountered is that I would like to access properties of a tag. I didn't find any solution on how to do it.
Solution with delegates
You could add a custom delegate protocol for your custom view
protocol CustomViewDelegate {
func didSelectCustomView(_ view: CustomView)
}
class CustomView: UIView {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myButton: UIButton!
weak var delegate: CustomViewDelegate?
#IBAction func buttonPressed(sender: UIButton) {
delegate?.didSelectCustomView(self)
}
}
And in your ViewController you have to check from which view to delegate call comes:
class ViewController: UIViewController {
#IBOutlet weak var view1: CustomView!
#IBOutlet weak var view2: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
view1.delegate = self;
view2.delegate = self;
}
}
extension ViewController: CustomViewDelegate {
func didSelectCustomView(_ view: CustomView) {
if view == view1 {
// view1 clicked
} else {
// view2 clicked
}
}
}
Solution with blocks
Instead of a delegate, you could add an optional block that is executed if the button in the view was clicked:
class CustomView: UIView {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myButton: UIButton!
var clicked: ((CustomView) -> Void)?
#IBAction func buttonPressed(sender: UIButton) {
clicked?(self)
}
}
And in your ViewController, you can add the block to your views:
class ViewController: UIViewController {
#IBOutlet weak var view1: CustomView!
#IBOutlet weak var view2: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
view1.clicked = { view in
}
view2.clicked = { view in
}
}
}

Passing data between view controllers inside page view controllers in Swift

Sorry if this particular problem has been asked about, I followed the answers on other threads but none of them seemed to work, but I just started learning Swift so all of this is pretty new to me.
So, I have a text field in two View Controllers and I want the third View Control to display a result based on the input from the other two controllers when I press a button.
I followed this tutorial and placed the text fields, label and button like I said before.
I placed my code (which you can see below) inside ViewControl.swift.
The problem is that when I attempt to run it I get a "Thread 1 :EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" error in the last two lines.
class ViewController: UIViewController {
var a: String = ""
var b: String = ""
#IBOutlet weak var aTextField: UITextField!
#IBOutlet weak var bTextField: UITextField!
#IBOutlet weak var calculateButton: UIButton!
#IBOutlet weak var resultLabel: UILabel!
#IBAction func calculateButtonPressed(_ sender: UIButton) {
let a = aTextField.text!;
let b = bTextField.text!;
I think that the error is from the data not passing between the views (because before I had everything in the same view and it worked fine), but since I only have one ViewController.swift file I couldn't figure out how to use a Segue.
Do not declare same variables multiple times. Remove let before a & b . You have already declared a & b globally and then tried to redeclare it inside IBAction
class ViewController: UIViewController {
var a: String = ""
var b: String = ""
#IBOutlet weak var aTextField: UITextField!
#IBOutlet weak var bTextField: UITextField!
#IBOutlet weak var calculateButton: UIButton!
#IBOutlet weak var resultLabel: UILabel!
#IBAction func calculateButtonPressed(_ sender: UIButton) {
a = aTextField.text!;
b = bTextField.text!;
Make sure your control outlets are setted properly.
In your two variables a & b are re-declared.Just update your code like below
#IBAction func calculateButtonPressed(_ sender: UIButton) {
self.a = aTextField.text!
self.b = bTextField.text!
}

How do you make a function which sets properties of other instances in swift?

I am trying to write the following code in a shorter way.
func colourChangeIfDeletionCancelled(word:Int){
for i in 0...selectedWords[word].count - 1 {
let square = selectedWords[word][i]
arrayOfRows[square.0][square.1].topToRight.backgroundColor = nil
arrayOfRows[square.0][square.1].topToLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].bottomToRight.backgroundColor = nil
arrayOfRows[square.0][square.1].bottomToLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].horizontalTube.backgroundColor = nil
arrayOfRows[square.0][square.1].verticalTube.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromRight.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromTop.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromBottom.backgroundColor = nil
}
}
This code works but I am sure there is a better (shorter) way to do write it but I am unsure how. I have tried to make a function that takes the subviews as a variable but am lost on how to do that. I'm sure this isn't the hardest problem but I am stuck on it and any help is appreciated.
Edit:
arrayOfRows is just an array of arrays of a class I have created called LetterSquareView
class LetterSquareView: UIView {
var letter:String!
#IBOutlet weak var topToLeft: UIView!
#IBOutlet weak var topToRight: UIView!
#IBOutlet weak var bottomToRight: UIView!
#IBOutlet weak var bottomToLeft: UIView!
#IBOutlet weak var horizontalTube: UIView!
#IBOutlet weak var verticalTube: UIView!
#IBOutlet weak var endFromLeft: UIView!
#IBOutlet weak var endFromRight: UIView!
#IBOutlet weak var endFromTop: UIView!
#IBOutlet weak var endFromBottom: UIView!
#IBOutlet weak var topToLeftWhiteSpace: UIView!
#IBOutlet weak var topToRightWhiteSpace: UIView!
#IBOutlet weak var bottomToRightWhiteSpace: UIView!
#IBOutlet weak var bottomToLeftWhiteSpace: UIView!
#IBOutlet weak var letterSquareViewView: LetterSquareViewView!
#IBOutlet var letterSquareView: UIView!
#IBOutlet weak var letterLbl: UILabel!
init(frame: CGRect, letter: String) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("LetterSquareView", owner: self, options: nil)
letterLbl.text = letter.capitalizedString
self.letter = letter
self.addSubview(letterSquareView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The subviews that I am trying to change the background colour of have a background colour set when they are created. I am trying to delete that colour when they are deleted. I thought there would be a function where you could input an array of the views and input a property of there's to set and it'd just be set but I can't find anything of the sort.
I guess the most straight forward way is to have:
func clearBackgroundColors(e: WhateverViewThisIs) {
for view in [e.topToRight, e.topToLeft, etc. ] {
view.backgroundColor = nil
}
}
clearBackgroundColors(arrayOfRows[square.0][square.1])
A good question to think is about responsibilities of the classes. I would argue the view in arrayOfRows[][] should have it's subviews private, knowledge of them is likely an implementation detail and not of public knowledge. So the clearBackgroundColors() method should be placed there instead of setting all colors from a more global place.
import UIKit
var arrayOfRows: [[UIView]] = [[]]
let square: (Int, Int)!
A way to make your touple code cleaner:
//extracting touple
let (row, col) = square
arrayOfRows[row][col].topToRight.backgroundColor = nil
Only use this is you are sure all the subviews of this view you want to make the background color nil.
//for all views
for arrayOfColumns in arrayOfRows {
for views in arrayOfColumns {
for subview in views.subviews {
subview.backgroundColor = nil
}
}
}
This is how I recommend to handle it. This also allows you to customize those views further with OOP design. Then you can
//with class type
//use this if there are other subviews
//make all your views in your list a CustomViewToMakeNil
class CustomViewToMakeNil: UIView {
//set the views content
}
for arrayOfColumns in arrayOfRows {
for views in arrayOfColumns {
for subview in views.subviews {
//check if they are of the type you want to make the color nil
if subview is CustomViewToMakeNil {
subview.backgroundColor = nil
}
}
}
}

Getting values from various Labels and doing simple calculation

I am trying to program a simple app in Xcode using Swift.
I have two labels which contain INT values which are incremented up/down by the user using Steppers as per below:
class ViewController: UIViewController {
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
#IBAction func stepperValueChanged(sender: UIStepper) {
valueLabel.text = Int(sender.value).description
}
#IBOutlet weak var valueLabel2: UILabel!
#IBOutlet weak var stepper2: UIStepper!
#IBAction func stepperValueChanged2(sender: UIStepper) {
valueLabel2.text = Int(sender.value).description
}
I want to then have another Label (label3) which takes the INTs from label1 & label2 and divides them.
So for example if the user has Label1 as '5' and label2 as '10' I want label3 to display '0.5'.
Is there an easy way that I can do this?
As I'm sure you can tell, I'm really new to iOS development so any help or advice would be greatly appreciated!
Cheers!
Although this is a simple program you should still use best practice and adopt a model-view-controller design. Your model (data) should be separate from view (in essence the way the data is displayed). In a more 'serious' application the model would typically be a separate class, but in this case you can use local properties.
Storing data in Int properties also means you won't have to convert back from strings when you want to perform calculations.
class ViewController: UIViewController {
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
#IBOutlet weak var valueLabel2: UILabel!
#IBOutlet weak var stepper2: UIStepper!
#IBOutlet weak var valueLabel3: UILabel!
var value1=0;
var value2=0;
#IBAction func stepperValueChanged(sender: UIStepper) {
self.value1=Int(sender.value);
self.valueLabel1.text="\(self.value1)";
self.updateResult();
}
#IBAction func stepperValueChanged2(sender: UIStepper) {
self.value2=Int(sender.value);
self.valueLabel1.text="\(self.value1)";
self.updateResult();
}
func updateResult {
if (self.value2 != 0) {
var result=Float(self.value1)/Float(self.value2)
self.valueLabel3.text="\(result)"
} else {
self.valueLabel3.text="Can't divide by 0"
}
}
}

Resources