How to detect focused UITextField in Container view from Parent View Controller - ios

I have a container view with multiple text boxes on it. I also have a button in Parent View controller(custom keypad). What I'm trying to do is select text box first & when I tap on the button I wanted some value to be populated to that last selected/focused textbox.
How can I do that? any alternative ways are welcome too. (I am having multiple container-views in the original code and try to use one keypad for all the views)
class MainViewController: UIViewController {
var weightVC : WeightViewController!
var focusedElement : UITextField
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "weight") {
weightVC = segue.destination as? WeightViewController
}
}
#IBAction func button1Clicked(_ sender: Any) {
if weightVC != nil {
weightVC.sampleTextBox1.text = "1"
//I want this sampleTextBox1 to be dynamic like weightVC.focusedInput = "1"
}
}
}
extension MainViewController:ChildToParentProtocol {
func setFocusedElement(with value: UITextField){
focusedElement = value
}
}
Container View Controller
protocol ChildToParentProtocol: class {
func setFocusedElement(with value:UITextField)
}
class WeightViewController: UIViewController {
weak var delegate: ChildToParentProtocol? = nil
#IBOutlet weak var sampleTextBox1: UITextField!
#IBOutlet weak var sampleTextBox2: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
// sampleTextBox1 Editing Did Begin event
#IBAction func editBeginSampleText1(_ sender: Any) {
print("edit begin")
delegate?.setFocusedElement(with: sampleTextBox1)
}
}
In other words, I simply want to keep a reference to last focused UITextFild when a button is tapped. Hope my requirement is clear enough. Please guide me if there is a way to achieve this.
Thanks

If I understood your question correctly you can keep track on which UITextField is tapped by using it's tag. And you can use UITextFieldDelegate to get the selected UITextField tag.
Consider the below code for WeightViewController
protocol ChildToParentProtocol: class {
//Changed value to Int for passing the tag.
func setFocusedElement(with value: Int)
}
import UIKit
class WeightViewController: UIViewController {
#IBOutlet weak var tf1: UITextField!
#IBOutlet weak var tf2: UITextField!
var selectedTFTag = 0
weak var delegate: ChildToParentProtocol? = nil
override func viewDidLoad() {
super.viewDidLoad()
//Assign delegate and tags to your TF
tf1.delegate = self
tf2.delegate = self
tf1.tag = 1
tf2.tag = 2
}
}
extension WeightViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
//Get the selected TF tag
selectedTFTag = textField.tag
//Pass tag to parent view
delegate?.setFocusedElement(with: selectedTFTag)
}
}
Now in your parent view ViewController you need to make some modification. I have added comments where I made changes to achieve your requirement.
import UIKit
//You need to confirm your ChildToParentProtocol with your UIViewController
class ViewController: UIViewController, ChildToParentProtocol {
var selectedTFTag = 0
var weightVC : WeightViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "weight" {
weightVC = segue.destination as? WeightViewController
//You need to pass delegate to containerview to make it working.
weightVC.delegate = self
}
}
#IBAction func btn1Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
#IBAction func btn2Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
func setFocusedElement(with value: Int) {
//Get selected TF tag with delegate
selectedTFTag = value
}
}
You can check THIS demo project for more info.

Related

Delegate function not called

I have two view controllers (ViewController and ActionViewController) and one manager (Brain), the second view controller is shown when a user tapped on a button by a show segue created in storyboard and to get back to the first I use a self.dismiss in the second view controller.
The user enter a number on ActionViewController that need to be retrieved in ViewController. So I created Brain to use the delegate pattern.
The problem is that the delegate function inside ViewController is never run, I read other SO answers but nothing work. I used print statement to know where the code is not running anymore and the only block not running is the didUpdatePrice inside ViewController
Here is the code
ViewController
class ViewController: UIViewController, BrainDelegate {
var brain = Brain()
#IBOutlet var scoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
brain.delegate = self
scoreLabel.layer.cornerRadius = 25
scoreLabel.layer.masksToBounds = true
}
func didUpdateScore(newScore: String) {
print("the new label is \(newScore)")
scoreLabel.text = newScore
}
}
ActionViewController
class ActionViewController: UIViewController {
var brain = Brain()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
brain.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Brain
protocol BrainDelegate {
func didUpdateScore(newScore: String)
}
struct Brain {
var delegate: BrainDelegate?
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
delegate?.didUpdateScore(newScore: String(newScore))
}
}
You dont need an additional Brain class/struct at all, You can achieve it with simple protocol and default extension of protocol.
Step 1: Select your show segue and provide an identifier to that in storyboard as shown below
Step 2: In your ViewController add prepare(for segue method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "testIdentifier" {
guard let destinationViewController = segue.destination as? ActionViewController else { return }
destinationViewController.delegate = self
}
}
Step 3: In your ActionViewController declare a weak property named delegate
class ActionViewController: UIViewController {
weak var delegate: BrainDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Step 4: Add class clause to your BrainDelegate (Class bound protocol) so that you can hold a weak reference to delegate
protocol BrainDelegate: class {
func didUpdateScore(newScore: String)
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool)
}
Step 5:
Add a default extension to BrainDelegate and provide default implementation of newAction(actualScore:
extension BrainDelegate {
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
self.didUpdateScore(newScore: String(newScore))
}
}
Step 6: In your ActionViewController simply trigger delegate methods as
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
This should do the job
First, on Brain you should use class, not struct. That is because when you use struct, passing the variable to another will make a copy, it will not use the same reference. And class will only copy the reference.
That means that your Brain struct will lose the delegate assigned on .delegate = self
second, you need to use the same instance on the second viewController and the first. like this:
on the first viewController
var brain = Brain()
// this one is the one that you will put your "brain.delegate = self"
on the second viewController, you will need to inject this variable from the first viewController into the second. That is to keep the same instance on both. And this will make the delegate callable.
to do this with storyboard you will do on the first ViewController:
// this function should be called when the next viewController should open.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination) {
case let vc as MyViewController:
vc.brain = self.brain
default:
break
}
}
super.prepare(for: segue, sender: sender)
}
inside the second viewController, use:
var brain: Brain?

Receive changing data into another controller Swift

I have a ViewController with a variable which value is changing every second ( from a sensor ).
I made another ViewController let's call it SensorViewController with a Label on the screen in which I want to display the value from the main ViewController.
If I use override func prepare(for segue: UIStoryboardSegue, sender: Any?) the value is send but only one time ( it doesn't refresh/update every second ).
What can I do to change the value from SensorViewController every time the value from ViewController is changing?
Example:
// ViewController example code:
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
distanta1 = String(byteArray[0]) // variable which is changing every second
#IBAction func distantaSenzori(_ sender: UIButton) { //button which send me to SensorViewController
self.performSegue(withIdentifier: "goToSenzori", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // this one is sending value only when I press the button from above ( I have to exit from SensorViewController and enter again to see updated value )
if segue.identifier == "goToSenzori"{
let destinatieVC = segue.destination as! SensorViewViewController
destinatieVC.distance1 = distanta1 } }
}
// SensorViewController code:
class SensorViewViewController: UIViewController {
#IBOutlet weak var distanta1: UILabel!
var distance1: String?
override func viewDidLoad() {
super.viewDidLoad()
distanta1.text = distance2 }
}
Thank you very much, guys! You are awesome!
For Frankenstein:
In class SensorViewViewController my code looks like that:
var distance1: String?
override func viewDidLoad() {
super.viewDidLoad()
distanta1.text = distance1
print("Distance 1 is \(distance1)")
// Do any additional setup after loading the view.
}
It's called only once and the value is nil. What should I modify at the code here so the value to be refreshed?
I think a cleaner solution is to create a shared manager for handling the sensor. After that, you can notify your objects about the changing value. Of course in your case your "sensor" is something bluetooth but what I wrote is only a template basically, you can fill in your necessary methods and objects, delegates, so on.
class SensorManager {
static let shared: SensorManager = SensorManager()
private var sensor: Sensor
private init() {
sensor = Sensor()
}
//MARK: - Public methods
func startTheSensor() {
//This is what you call to start your sensor
}
func getSensorData() -> YourData {
//This is from where your objects can read the sensor data
}
//MARK: - Private methods -
private func didSensorUpdatedValue() {
//This is called whenever your sensor updates
.
.
.
let newSensorValue = "yourValue"
NotificationCenter.default.post(name: .init("SensorDataChanged"), object: nil)
}
}
In your viewcontroller:
deinit() {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
weak var this = self
NotificationCenter.default.addObserver(this, selector: #selector(didSensorValueChanged), name: .init("SensorDataChanged"), object: nil)
}
#objc func didSensorValueChanged() {
SensorManager.shared.getSensorData()
}
You need to add an observer and keep reference to the destination view controller to keep passing in the new value that has been changed to the destination view controller. Here's how:
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var distanta1 = String(byteArray[0]) {
didSet {
destinatieVC?.distance1 = distanta1
}
}
//...
var destinatieVC: SensorViewViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSenzori" {
destinatieVC = segue.destination as? SensorViewViewController
}
}
}
In SensorViewViewController:
var distance1: String? {
didSet {
print(distance1 ?? "")
}
}
Better approach: Set the destinatieVC?.distanta1.text = distanta1 directly if you're not doing anything else in the didSet block avoid the distance1 property entirely.

Passing an object with data from one view to another view in Swift using Segue

I am trying to pass an object with data from one view to another view using Swift4. However, I get this problem when trying to print.
Here is the message that I get:
2018-03-21 21:47:03.542578-0400 Sudoku[64427:1070575] <UIView:
0x7fd28bc0b4c0; frame = (0 0; 414 736); autoresize = W+H; layer =
<CALayer: 0x604000038500>>'s window is not equal to
<Sudoku.setupSudoku: 0x7fd28bf256c0>'s view's window!
Here is the picture of my View. Identifier for segue: setupScreen
Here is the code in setPlayer:
class setPlayer: UIViewController {
var singleUser:playerInfor=playerInfor()
var randomGame:Int = -1
var playerName:String=""
#IBOutlet weak var txtPlayerName: UITextField!
var sizeOfSudoku:Int = 9 //for project 1, let take one size of sudoku
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "Communication-Network-Vector-Illustration.jpg")!)
self.txtPlayerName.layer.borderWidth=2.0
self.txtPlayerName.layer.borderColor=UIColor.blue.cgColor
// Do any additional setup after loading the view.
}
func randomNumber(){
self.randomGame=Int(arc4random_uniform(3)+1)
}
func initialize(){
//if(size)
if txtPlayerName.text?.isEmpty ?? true {
self.playerName="Player1"
} else {
self.playerName=self.txtPlayerName.text!
}
self.singleUser=playerInfor(playerName:self.playerName,size:self.sizeOfSudoku,time:0, randomSudoku:self.randomGame)
//print (self.singleUser.size)
}
#IBAction func startButton(_ sender: Any) {
initialize()
performSegue(withIdentifier: "setupScreen", sender: self.singleUser)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "setupScreen"{
if let destination=segue.destination as?setupSudoku{
destination.objectPlayer=sender as?playerInfor
print(singleUser.playerName)
}
}
}
#IBAction func size9(_ sender: Any) {
self.sizeOfSudoku=9
}
#IBAction func Size6(_ sender: Any) {
self.sizeOfSudoku=9
}
Here is playerInfor
import Foundation
import UIKit
class playerInfor{
var playerName:String=""
var size:Int=0
var time:Int=var randomSudoku:Int=0
init(playerName:String,size:Int,time:Int,randomSudoku:Int) {
self.playerName=playerName
self.size=size
self.time=time
self.randomSudoku=randomSudoku
}
}
Here is setupSudoku
class setupSudoku: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var stackViewButtons: UIStackView!
#IBOutlet weak var myCollectionView: UICollectionView!
var newNumber=0
var objectPlayer:playerInfor!
override func viewDidLoad() {
super.viewDidLoad()
print(self.objectPlayer?.time)
}
}
You have setup your segue incorrectly in your storyboard, and it is attempting to segue twice. To confirm this is the case, select your segue in your storyboard and check if your button highlights, like this:
To resolve, delete this segue and create a new one, by control-dragging from the view controller (not the button) to the destination, like so:
Make sure to reset the new segues identifier back to setupScreen. Then you should be good to go!

how to pass a UI ImageView from one view controller to another? swift 3

I am trying to pass this UI Image View from one view controller to another on the same storyboard. I have already passed a UI TextField to a UI Label and UI Button. Now I need to do it with a UI Image View.
Here are my two view controllers.
import UIKit
class PhotoShareViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var contentTextView: UITextView!
#IBOutlet weak var thatTextField: UITextField!
#IBOutlet weak var thisTextField: UITextField!
var presenter: PhotoShareModuleInterface!
var image: UIImage!
#IBAction func thisUploadPhoto(_ sender: Any) {
if thisTextField.text != "" && thatTextField.text != ""
{
performSegue(withIdentifier: "segue", sender: nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var photoShareLabelViewController = segue.destination as! PhotoShareLabelViewController
photoShareLabelViewController.thisString = thisTextField.text!
photoShareLabelViewController.thatString = thatTextField.text!
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
imageView.image = image
}
override var prefersStatusBarHidden: Bool {
return true
}
#IBAction func didTapCancel(_ sender: AnyObject) {
presenter.cancel()
presenter.pop()
}
/* #IBAction func didTapDone(_ sender: AnyObject) {
guard let message = thatTextField.text, !message.isEmpty else {
return
}
guard let messageOne = thisTextField.text, !messageOne.isEmpty else {
return
}
presenter.finish(with: image, content:message)
presenter.dismiss()
}
}
*/
}
extension PhotoShareViewController: PhotoShareViewInterface {
var controller: UIViewController? {
return self
}
}
if someone could tell me how to send the image from one view controller into another that would be awesome!
You didn't show PhotoShareLabelViewController, but presumably it has:
var thisString: String?
Add
var thisImage: UIImage?
And then pass it along the same way as thisString
photoShareLabelViewController.thisImage = imageView.image
And pick it up in the viewDidLoad of PhotoShareLabelViewController (which is probably where you pick up thisString). Something like:
self.imageView.image = self.thisImage
But, using whatever variable name you are using.
In short, just copy what you are doing for thisString, but use a UIImage as the type instead of String.
NOTE: You are not passing a UIImageView from one VC to another (that is impossible). You are passing the image from an imageview from one VC to another.

Changing label text inside method

I'm using delegation to pass back information from a view controller.
This is the method
func writeValueBack(value: String) {
self.label.text = value
}
The function gets called and all is great apart from the label doesn't update.
The label has a value and is not returning nil, I checked with this line
println(self.label.text)
It prints the value of 'value'
So that means that the label's text is being set to 'value' but it's not updating.
I even tried using the main thread but no luck
func writeValueBack(value: String) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.label.text = value
})
}
I just don't know what the problem is.
Any help would be great.
Protocol:
protocol writeValueBackDelegate {
func writeValueBack(value: String)
}
EDIT:
Code for my view controller:
//
// ViewController.swift
// DelegateTesting
//
// Created by Alex Catchpole on 30/11/2014.
// Copyright (c) 2014 Alex Catchpole. All rights reserved.
//
import UIKit
class ViewController: UIViewController, writeValueBackDelegate {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MainSegue" {
var vc = segue.destinationViewController as SecondViewController
vc.labelText = textField.text
vc.delegate = self
}
}
func writeValueBack(value: String) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.label.text = value
})
}
#IBAction func button(sender: AnyObject) {
self.label.text = textField.text
}
#IBAction func segue(sender: AnyObject) {
performSegueWithIdentifier("MainSegue", sender: self)
}
}
Second ViewController source:
//
// SecondViewController.swift
// DelegateTesting
//
// Created by Alex Catchpole on 30/11/2014.
// Copyright (c) 2014 Alex Catchpole. All rights reserved.
//
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var textField: UITextField!
var labelText: String!
var delegate: writeValueBackDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
label.text = labelText
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func button(sender: AnyObject) {
label.text = textField.text
}
#IBAction func segueBack(sender: AnyObject) {
var editedText = label.text
performSegueWithIdentifier("SecondSegue", sender: self)
if (delegate != nil) {
delegate?.writeValueBack(editedText)
println("working")
}
}
}
The issue is in your second view controller, which as rdelmar pointed out creates a new instance of your first view controller instead of navigating back to the original instance.
To fix this, you could use dismissViewControllerAnimated:completion instead of performing your second segue. But an unwind segue would lead to simpler code, and can be achieved by adding this to your first view controller:
#IBAction func unwindFromSecond(unwindSegue: UIStoryboardSegue) {
if let secondViewController = unwindSegue.sourceViewController as? SecondViewController {
label.text = secondViewController.label.text
// Or whatever you need to retrieve data from the second controller
}
}
Then in your storyboard create an unwind segue from the second view controller. For example if you have a Dismiss button, control-drag from this button to the Exit icon in your second view controller scene and choosing unwindFromSecond. For detailed steps see the answer to this other question: What are Unwind segues for and how do you use them?
You can now remove the writeValueBackDelegate declaration and associated variables, the writeValueBack method and the second view controller's segueBack method

Resources