How to pass data forward between View Controllers - Swift - ios

Hi I would like to know why every-time i ask for the emailCatched value in the second VC, I always get nil. Isn't how you pass data forward? Sorry if it's very basic.
I wrote the same code for passing the password from VC2 to VC3 and it works, it's weird. Thanks
First VC :
class EmailAddressViewController: UIViewController {
let emailField: UITextField = {
let emailField = UITextField()
return emailField
}()
private let continueButton: UIButton = {
let button = UIButton()
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
// SubViews
view.backgroundColor = .white
view.frame = view.bounds
view.addSubview(emailField)
view.addSubview(continueButton)
// I omitted the frames
continueButton.addTarget(self,
action: #selector(continueButtonTapped),
for: .touchUpInside)
}
#objc private func continueButtonTapped() {
let email = emailField.text,
let vc = PasswordViewController()
vc.emailCatched = email
self.present(PasswordViewController(), animated: true)
}
}
Second VC (PasswordViewController) :
class PasswordViewController: UIViewController {
private let passwordField: UITextField = {
let passwordField = UITextField()
passwordField.autocapitalizationType = .none
passwordField.autocorrectionType = .no
passwordField.returnKeyType = .next
passwordField.layer.cornerRadius = 10
passwordField.layer.borderWidth = 1
passwordField.layer.borderColor = UIColor.darkGray.cgColor
passwordField.placeholder = "Mot de passe"
passwordField.textAlignment = .center
passwordField.backgroundColor = .white
passwordField.translatesAutoresizingMaskIntoConstraints = false
passwordField.isSecureTextEntry = true
return passwordField
}()
private let continueButton: UIButton = {
let button = UIButton()
button.setTitle("Continuer", for: .normal)
button.backgroundColor = .systemGreen
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 25
button.layer.masksToBounds = true
button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
var emailCatched: String?
// MARK: viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
print(emailCatched!) // returns error or nil
// SubViews
view.backgroundColor = .white
view.frame = view.bounds
view.addSubview(passwordField)
view.addSubview(continueButton)
continueButton.addTarget(self,
action: #selector(continueButtonTapped),
for: .touchUpInside)
}
// function bouton continuer
#objc private func continueButtonTapped() {
passwordField.resignFirstResponder()
guard let password = passwordField.text,
!password.isEmpty,
password.count >= 8 else {
alertInvalidPassword()
return
}
let vc = NamesViewController()
// vc.emailCatched = emailCatched!
vc.passwordCatched = password
HapticsManager.shared.vibrate(for: .success)
self.presenter(vc, animated: false, pushing: true, completion: nil)
}
}

Related

Protocol and delegate pattern not calling the method

Implementing a protocol/delegate pattern as suggested by expert #DonMag, i am unable to get the code working...
I show a ratings window like below and when the user changes the ratings , i want the ratings emoji to update and the controller to pop, i also use animation, now for view i have a separate ratings class and for controller a separate class,
The problem is this function gets tapped , i can detect it but it does not reach the protocol method
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
Controller class
import UIKit
import CoreData
protocol RatingsPresentation: class {
func defineTheratings(_ ratings: String)
}
class RatingsViewController: UIViewController, RatingsPresentation {
func defineTheratings(_ ratings: String) {
print("ratings seleced\(ratings)")
self.restaurant.rating = ratings
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.saveContext()
}
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController?.popViewController(animated: true)
}
}
var restaurant: Restaurant!
var rates = RatingsView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view)
//Delegates
let vc = RatingsView()
// set the rating delegate
vc.ratingDelegate = self
//
if let restaurantImage = restaurant.image {
rates.bkgImageView.image = UIImage(data: restaurantImage as Data)
}
rates.crossBtn.addTarget(self, action: #selector(closeRatings), for: .touchUpInside)
let animateCross = CGAffineTransform(translationX: 1000, y: 0)
rates.crossBtn.transform = animateCross
}
#objc func closeRatings() {
navigationController?.popViewController(animated: true)
}
}
View Class
import UIKit
class RatingsView: UIViewController {
var bkgImageView = UIImageView()
var crossBtn = UIButton()
var btnCollection: [UIButton] = []
weak var ratingDelegate: RatingsPresentation?
override func viewDidLoad() {
super.viewDidLoad()
let ratings: [String] = ["cool", "happy","love", "sad", "angry"]
let animationBlur = UIBlurEffect(style: .dark)
let visualBlurView = UIVisualEffectView(effect: animationBlur)
// add elements to self
view.addSubview(bkgImageView)
view.addSubview(visualBlurView)
bkgImageView.translatesAutoresizingMaskIntoConstraints = false
visualBlurView.translatesAutoresizingMaskIntoConstraints = false
bkgImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
bkgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bkgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bkgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// constrain visualBlurView to all 4 sides
visualBlurView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
bkgImageView.contentMode = .scaleAspectFill
// Do any additional setup after loading the view.
//Add stackView
let stackEmoji = UIStackView()
stackEmoji.translatesAutoresizingMaskIntoConstraints = false
stackEmoji.axis = .vertical
stackEmoji.spacing = 5
stackEmoji.distribution = .fill
stackEmoji.alignment = .top
let font = UIFont(name: "Rubik-Medium", size: 28)
let fontM = UIFontMetrics(forTextStyle: .body)
ratings.forEach { (btn) in
let b = UIButton()
b.setTitle(btn, for: .normal)
b.titleLabel?.font = fontM.scaledFont(for: font!)
b.setImage(UIImage(named: btn), for: .normal)
stackEmoji.addArrangedSubview(b)
btnCollection.append(b)
//btn animation
let sizeAnimation = CGAffineTransform(scaleX: 5, y: 5)
let positionAnimation = CGAffineTransform(translationX: 1000, y: 0)
let combinedAninaton = sizeAnimation.concatenating(positionAnimation)
b.transform = combinedAninaton
b.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stackEmoji)
stackEmoji.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackEmoji.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// if let img = UIImage(named: "cross") {
// crossBtn.setImage(img, for: [])
// }
crossBtn.setTitle("X", for: [])
crossBtn.setTitleColor(.white, for: .normal)
crossBtn.setTitleColor(.gray, for: .highlighted)
crossBtn.titleLabel?.font = UIFont.systemFont(ofSize: 44, weight: .bold)
view.addSubview(crossBtn)
crossBtn.translatesAutoresizingMaskIntoConstraints = false
crossBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
crossBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 150).isActive = true
}
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
}
Problem is here
var rates = RatingsView() //// shown 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view) /////// you add this as a subview
//Delegates
let vc = RatingsView() //////// this is useless - not shown 1
// set the rating delegate
vc.ratingDelegate = self ////// and set the delegate for this non-shown
So you need to remove let vc = RatingsView() and set the delegate for
rates.ratingDelegate = self

Swift: Change value of another ViewController with delegation programmatically

I am trying to change the value of one ViewController by clicking a button inside a second ViewController with delegation.
But so far it only prints a message, but doesn't change the value.
I have this class where I defined a view:
class CounterView: UIView {
public var creditPointValue = Int()
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .cyan
self.addSubview(label)
label.text = "Credit Points: \(creditPointValue)"
label.translatesAutoresizingMaskIntoConstraints = false;
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
func changeCreditPointValue(value:Int){
creditPointValue = creditPointValue + value
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am using that view inside this ViewController and I want to manipulate the variable "creditPointValue":
protocol AddCreditsDelegate {
func addCreditsToCounter()
}
class ViewController: UIViewController {
var delegate: AddCreditsDelegate?
var counterView = CounterView()
let label = UILabel()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .white
view.addSubview(button)
button.backgroundColor = .red
button.setTitle("View2", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(changeView), for: .touchUpInside)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
view.addSubview(counterView)
counterView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
counterView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
counterView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
//counterView.frame.size.height = 30
counterView.bottomAnchor.constraint(equalTo: counterView.topAnchor, constant: 50).isActive = true
}
#objc func changeView(){
delegate?.addCreditsToCounter()
navigationController?.pushViewController(ViewController2(), animated: true)
}
}
And inside this second ViewController I am trying to change the value by clicking the button I added to the view:
class ViewController2: UIViewController {
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.backgroundColor = .red
button.setTitle("add Credits", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(addCreditsButton), for: .touchUpInside)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func addCreditsButton(){
addCreditsToCounter()
}
}
extension ViewController2 : AddCreditsDelegate{
func addCreditsToCounter() {
let vc = ViewController()
vc.delegate = self
vc.counterView.creditPointValue += 5
print("pressed")
}
}
So far only the message "pressed" gets printed each time I click the button and I don't even know if I am going into the right direction with my approach trying to use delegation.
You should not create a new ViewController instance each time you call ViewController2.addCreditsToCounter, you already have a ViewController that creates a ViewController2 instance in ViewController.changeView. Just store a weak reference to ViewController in ViewController2 using delegate. In such case ViewController (not ViewController2) should conform to AddCreditsDelegate.
First of all, replace
protocol AddCreditsDelegate {
func addCreditsToCounter()
}
with
protocol AddCreditsDelegate: AnyObject {
func addCreditsToCounter()
}
Then add weak var delegate: AddCreditsDelegate? to ViewController2 and remove ViewController.delegate. Remove ViewController2.addCreditsToCounter, ViewController2 should not conform to AddCreditsDelegate. It means that in ViewController2.addCreditsButton you should call delegate?.addCreditsToCounter(), not addCreditsToCounter().
ViewController should conform to AddCreditsDelegate:
extension ViewController: AddCreditsDelegate {
func addCreditsToCounter() {
counterView.creditPointValue += 5
print("pressed")
}
}
And don't forget to initialize ViewController2.delegate. Replace your ViewController.changeView implementation with
let controller = ViewController2()
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
You create a new instance here
let vc = ViewController()
instead you need
#objc func changeView() {
let sec = ViewController2(),
sec.delegate = self
navigationController?.pushViewController( animated: true)
}
Then declare this inside the SecondVC
weak var delegate:ViewController?
Finally
func addCreditsToCounter() {
delegate?.counterView.creditPointValue += 5
print("pressed")
}

What error am I making when trying to pass data between ViewControllers using closures?

My main View Controller has an embedded UITabbarController in it. There are 2 subVCs: viewControllerONE & viewControllerTWO.
viewControllerONE displays a label showing the user's current score, and viewControllerTWO displays a button, pressing which should display an error window on viewControllerONE.
(For the sake of simplicity, the user has to manually navigate to viewControllerONE using the tab bar to see the error, ie, pressing the button on viewControllerTWO doesn't take you to viewControllerONE and then display the errorWindow.)
The error window is a simple UIView class.
From SO, I have learnt that the best way to pass data in swift isn't delegates but closures, as the former is more of an objective C design, and so I've used closures to pass data between view controllers.
So here is my viewControllerONE code:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
let VC = ViewControllerTWO()
VC.callback = { [weak self ] in
let error = errorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}
and here is my viewControllerTWO code:
class ViewControllerTWO: UIViewController {
var callback: (() -> Void)?
let buttonTwo: UIButton = {
let button = UIButton()
button.setTitle("HIT THIS BUTTON!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonTwoPressed), for: .touchUpInside)
button.layer.cornerRadius = 8
return button
}()
#objc func buttonTwoPressed() {
print("PRESSEDDDD")
callback?()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(buttonTwo)
NSLayoutConstraint.activate([
buttonTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
buttonTwo.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
And here is the error view:
class ErrorView: UIView {
fileprivate let dismissButton: UIButton = {
let button = UIButton()
button.setTitle("DISMISS!!!!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
button.layer.cornerRadius = 12
return button
}()
#objc func dismissButtonPressed() {
self.errorGoAway()
}
fileprivate let errorViewBox: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.layer.cornerRadius = 24
return v
}()
#objc fileprivate func errorGoAway() {
self.alpha = 0
}
#objc fileprivate func errorShow() {
self.alpha = 1
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(errorGoAway)))
self.backgroundColor = UIColor.gray
self.backgroundColor?.withAlphaComponent(0.8)
self.frame = UIScreen.main.bounds
self.addSubview(errorViewBox)
errorViewBox.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
errorViewBox.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
errorViewBox.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7).isActive = true
errorViewBox.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.45).isActive = true
errorViewBox.addSubview(dismissButton)
dismissButton.leadingAnchor.constraint(equalTo: errorViewBox.leadingAnchor).isActive = true
dismissButton.trailingAnchor.constraint(equalTo: errorViewBox.trailingAnchor).isActive = true
dismissButton.centerYAnchor.constraint(equalTo: errorViewBox.centerYAnchor).isActive = true
dismissButton.heightAnchor.constraint(equalTo: errorViewBox.heightAnchor, multiplier: 0.15).isActive = true
errorShow()
}
You need to get your controller from tabBarController instead of create new instance:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = .blue
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
var VC: ViewControllerTWO?
let arrayOfVC = self.tabBarController?.viewControllers ?? []
for item in arrayOfVC {
if let secondVC = item as? ViewControllerTWO {
VC = secondVC
break
}
}
VC?.callback = { [weak self ] in
let error = ErrorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}

UIScrollView didn't scrolling?

I'm creating a UIScrollView from code.The only problem is the scrolling is not working go through lot's of the post but didn't find anything.
Here is my code Any help greatly Appreciated.
class ShopDetailviewController : UIViewController {
var MainScrollView : UIScrollView?
let viewForScrollview : UIView = {
let view = UIView()
view.backgroundColor = UIColor.darkGray
return view
}()
let shopNameandAddresView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
return view
}()
let timePaymentDetail : UIView = {
let view = UIView()
view.backgroundColor = UIColor.blue
return view
}()
let shopImage : UIImageView = {
let shopeImage = UIImageView()
shopeImage.backgroundColor = UIColor.green
return shopeImage
}()
let ratingStarLable : UILabel = {
let ratingStratLable = UILabel()
ratingStratLable.backgroundColor = UIColor.red
ratingStratLable.text = "3.5"
return ratingStratLable
}()
let shopName : UILabel = {
let shopName = UILabel()
shopName.backgroundColor = UIColor.gray
shopName.text = "Kimling"
return shopName
}()
let shopLocation : UILabel = {
let shopLocation = UILabel()
shopLocation.backgroundColor = UIColor.white
shopLocation.text = "Kondhwa"
return shopLocation
}()
let seprater : UIView = {
let seprater = UIView()
seprater.backgroundColor = UIColor.white
return seprater
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
navigationController?.navigationBar.tintColor = .white
view.backgroundColor = UIColor.white
}
override func viewDidLoad() {
super.viewDidLoad()
MainScrollView = UIScrollView()
MainScrollView?.isScrollEnabled = true
MainScrollView?.indicatorStyle = .default
MainScrollView?.contentSize = CGSize(width:self.view.bounds.width, height: 8000)
MainScrollView?.backgroundColor = UIColor.purple
setupViews()
}
func setupViews(){
view.addSubview(MainScrollView!)
MainScrollView?.addSubview(viewForScrollview)
viewForScrollview.addSubview(shopImage)
viewForScrollview.addSubview(shopNameandAddresView)
viewForScrollview.addSubview(timePaymentDetail)
addConstraintsWithFormat("H:|-0-[v0(414)]-0-|", views: MainScrollView!)
addConstraintsWithFormat("V:|-0-[v0(8000)]", views: MainScrollView!)
addConstraintsWithFormat("H:|-0-[v0(414)]-0-|", views: viewForScrollview)
addConstraintsWithFormat("V:|-0-[v0(8000)]", views: viewForScrollview)
addConstraintsWithFormat("V:|-0-[v0(1000)]-0-[v1(1000)]", views: shopImage,shopNameandAddresView)
addConstraintsWithFormat("H:|-0-[v0]-0-|", views: shopImage)
addConstraintsWithFormat("H:|-0-[v0]-0-|", views: shopNameandAddresView)
}
}
Use as this
override func viewDidLayoutSubviews()
{
MainScrollView?.contentSize = CGSize(width:self.view.bounds.width, height: 8000)
}
It seems like the in viewDidLoad layout is not finished so the scrollView wont scroll because it has no proper contentSize try setting the contentSize in ViewWillAppear as
MainScrollView?.contentSize = CGSize(width:self.view.bounds.width, height: 8000)

FBSDKLoginManager withReadPermissions error

I am having an issue with converting the value of a UIcollectionViewCell to the argument type of a UIViewController inside my handleCustomFBButton() function below. I believe it is something wrong with setting my delegates and/or datasource...thanks for any and all help!
import UIKit
import FBSDKLoginKit
import Firebase
class LoginCell: UICollectionViewCell, FBSDKLoginButtonDelegate {
let logoImageView: UIImageView = {
let image = UIImage(named: "me")
let imageView = UIImageView(image: image)
return imageView
}()
let emailTextField: LeftPaddedTextField = {
let textField = LeftPaddedTextField()
textField.placeholder = "Enter Email"
textField.layer.borderColor = UIColor.lightGray.cgColor
textField.layer.borderWidth = 1
textField.keyboardType = .emailAddress
return textField
}()
let passwordTextField: LeftPaddedTextField = {
let textField = LeftPaddedTextField()
textField.placeholder = "Enter Password"
textField.layer.borderColor = UIColor.lightGray.cgColor
textField.layer.borderWidth = 1
textField.isSecureTextEntry = true
return textField
}()
lazy var loginButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = .orange
button.setTitle("Log in", for: .normal)
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
return button
}()
lazy var FBloginButton: UIButton = {
let customButton = UIButton(type: .system)
customButton.backgroundColor = UIColor.blue
customButton.setTitle("Custom button here", for: .normal)
customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
customButton.setTitleColor(.white, for: .normal)
return customButton
customButton.addTarget(self, action: #selector(handleCustomFBButton), for: .touchUpInside)
}()
// error happens on the from: self
func handleCustomFBButton() {
FBSDKLoginManager().logIn(withReadPermissions: ["email", "public_profile"], from: self) { (result, err) in
if err != nil {
print("Custom FB Login Button Failed")
return
}
self.showEmailAddress()
}
}

Resources