iOS - How to set imageview of a parent view controller through child container? - swift

I am currently using a container view and I want to change the value of parent view controller imageView through child view controller using delegates, but it always returns nil.
import UIKit
protocol updateImage {
func userIsDone(image:UIImage)
}
class ViewController: UIViewController, updateImage{
#IBOutlet weak var imageView:UIImageView!
var image = UIImage(named: "hello.png")
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image=self.image
}
func userIsDone(image: UIImage) {
self.image=image
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerChild"{
let nextView = segue.destinationViewController as! ControllerChild
nextView.image=self.image
nextView.delegate=self
}
}
}
class ControllerChild:UIViewController{
var image=UIImage(named: "newhello.png")
var delegate: updateImage? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func changeImage(sender:UIButton){
if(delegate != nil){
self.delegate!.userIsDone(self.image!)
print("I am Called!")
}
}
}

Remove this line:
nextView.image = image
And change the userIsDone function to:
func userIsDone(image: UIImage) {
imageView.image = image
}
As side notes:
In Swift you don't need to use self outside of blocks/closures. For example, you don't need to self.imageView.image = self.image -- just imageView.image = image will do.
Protocols should be upper-cased, like UpdateImage (instead of updateImage).
You don't need that extra .png to reference images, just the title, E.g.: "hello".
Apple describes Swift code conventions in their awesome Swift book.
Here is the code, refactored (Swift 3):
import UIKit
protocol UpdateImageProtocol {
func userIsDone(image: UIImage)
}
class ViewController: UIViewController, UpdateImageProtocol {
#IBOutlet weak var imageView: UIImageView!
var image = UIImage(named: "hello")
override func viewDidLoad() {
super.viewDidLoad()
userIsDone(image: image!)
}
func userIsDone(image: UIImage) {
imageView.image = image
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "containerChild" {
let nextView = segue.destination as! ChildViewController
nextView.delegate = self
}
}
}
class ChildViewController: UIViewController {
var image = UIImage(named: "newHello")
var delegate: UpdateImageProtocol?
#IBAction func changeImage(sender: UIButton) {
delegate?.userIsDone(image: image!)
}
}

Related

Swift delegate beetween two VC without segue

I have 3 classes:
ChatLogControoller
GetImageFromLibraty(NSObject class)
ImagePreviewViewController
I want to press a clip from the first VC, then open the media library to pick an image. Then the selected image is passed to the third VC as a previewController. Then if I select 'done' I want to pass it to the first VC.
1st VC
class ChatLogControoller: UICollectionViewController, UICollectionViewDelegateFlowLayout, NSFetchedResultsControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, DataSentDelegate {
func recievePhoto(data: UIImage) {
imageFromView = data
print("-------\(imageFromView = data)")
}
override func viewDidLoad() {
super.viewDidLoad()
let vc = ImagePreviewController()
self.vc.delegate = self
}
2nd class its just picker of image, so i pass image to 3rd VC and this image appears on imageView of 3rd VC successfully!
my 3rd VC
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class PreviewController: UIViewController, UIScrollViewDelegate {
var delegate : DataSentDelegate? = nil
var aImageView: UIImageView!
var aImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(actionSend))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(actionBack))
}
#objc func actionBack() {
dismiss(animated: false, completion: nil)
}
#objc func actionSend() {
let data = aImageView.image
delegate?.recievePhoto(data: data!)
dismiss(animated: true, completion: nil)
}
You need to create one more protocol in your SecondViewController to Pass that delegate from ThirdViewController to FirstViewController.
FirstViewController:
import UIKit
class ViewController: UIViewController, DataSentDelegate, dataSentDelegate {
#IBOutlet weak var imagefromThirdVC: UIImageView!
var thirdVCImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonTapped(_ sender: Any) {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func goToThirdVC() {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func recievePhoto(data: UIImage) {
thirdVCImage = data
imagefromThirdVC.image = thirdVCImage
}
}
SecondViewController:
import UIKit
protocol dataSentDelegate {
func goToThirdVC()
}
class ViewController2: UIViewController {
#IBOutlet weak var passingImage: UIImageView!
var delegate: dataSentDelegate? = nil
var images: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
images = UIImage(named: "screen")
}
#IBAction func actionButton(_ sender: Any) {
self.delegate?.goToThirdVC()
}
}
ThirdViewController:
import UIKit
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class ViewController3: UIViewController {
var delegate: DataSentDelegate? = nil
#IBOutlet weak var passedImageView: UIImageView!
var passedImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
passedImage = UIImage(named: "screen")
passedImageView.image = passedImage
}
#IBAction func action(_ sender: Any) {
let data = passedImageView.image
delegate?.recievePhoto(data: data!)
// delegate?.goToFirstVC()
guard let viewControllers = self.navigationController?.viewControllers else {
return
}
for firstViewController in viewControllers {
if firstViewController is ViewController {
self.navigationController?.popToViewController(firstViewController, animated: true)
break
}
}
}
}

Can't pass value from FirstVC to SecondVC using segue

I have two ViewControllers connected via Show segue. I need to pass NSSlider's value from ViewController to SecondViewCotroller.
So, moving slider in ViewController a variable updates in SecondViewController.
How to update a value of imagesQty variable?
// FIRST VIEW CONTROLLER
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var slider: NSSlider!
#IBOutlet weak var photosLabel: NSTextField!
#IBAction func segueData(_ sender: NSSlider) {
photosLabel.stringValue = String(slider.intValue) + " photos"
self.performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "SegueIdentifierForSecondVC"), sender: slider)
}
func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {
if segue.identifier!.rawValue == "SegueIdentifierForSecondVC" {
if let secondViewController =
segue.destinationController as? SecondViewController {
secondViewController.imagesQty = slider.integerValue
}
}
}
}
and
// SECOND VIEW CONTROLLER
import Cocoa
class SecondViewController: NSViewController {
var imagesQty = 30
override func viewWillAppear() {
super.viewWillAppear()
self.view.wantsLayer = true
print("viewWillAppear – Qty:\(imagesQty)")
//let arrayOfViews: [NSImageView] = [view01...view12]
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Desktop/ArrayOfElements")
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).reversed()
let photos = fileURLs.filter { $0.pathExtension == "jpg" }
for view in arrayOfViews {
//"imagesQty" is here
let i = Int(arc4random_uniform(UInt32(imagesQty-1)))
let image = NSImage(data: try Data(contentsOf: photos[i]))
view.image = image
view.imageScaling = .scaleNone
}
} catch {
print(error)
}
}
First of all the purpose and benefit of NSStoryboardSegue.Identifier is to create an extension to be able to avoid literals.
extension NSStoryboardSegue.Identifier {
static let secondVC = NSStoryboardSegue.Identifier("SegueIdentifierForSecondVC")
}
Then you can write
self.performSegue(withIdentifier: .secondVC, sender: slider)
and
if segue.identifier! == .secondVC { ...
This error occurs because imagesQty is declared in viewWillAppear rather than on the top level of the class.
Change it to
class SecondViewController: NSViewController {
var imagesQty = 30 // Int is inferred
// override func viewWillAppear() {
// super.viewWillAppear()
// }
}
There is another mistake: The signature of prepare(for segue is wrong. It must be
func prepare(for segue: NSStoryboardSegue, sender: Any?) {
You can‘t change the value because the var is defined in the function and not in the class.
Make your var a class property and it should work.
class SecondViewController: UIViewController {
var imagesQty: Int = 30
...
}

How do I call a function from a different view controller?

I have what is basically a notes app, and I am trying to call a function that is in one view controller from another. How do I do this, or is there a better way I should do it?
Here's where I am trying to call the function from
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
#IBAction func funcButTouched(sender: UIButton)
{
// where i want to call the function
}
}
And where the function is
class ListTableViewController: UITableViewController
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
}
I've tried just creating an instance of ListTableViewController and just calling the function that way but I get an error.
You could use delegation.
First, declare a delegate protocol. This should be in your NoteDetailViewController.swift file but outside of the class declaration:
protocol NoteDetailViewControllerDelegate: class {
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController)
}
Next, add a delegate property to your NoteDetailViewController:
weak var delegate: NoteDetailViewControllerDelegate?
Now, we use the #IBAction to tell the delegate, which will be the ListTableViewController:
#IBAction func funcButTouched(sender: UIButton)
{
delegate?.noteDetailViewControllerButtonTouched(self)
}
Finally, back in ListTableViewController (assuming this controller is the one directly before a NoteDetailViewController is shown), conform to the protocol and use prepareForSegue to set the delegate to itself:
class ListTableViewController: UITableViewController, NoteDetailViewControllerDelegate {
// ... more stuff ...
// Implement the delegate protocol
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController) {
// Do something! The button was pressed!
wordCount()
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.delegate = self
}
}
}
You should finish with something like this:
NoteDetailViewController.swift:
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
weak var delegate: NoteDetailViewControllerDelegate?
#IBAction func funcButTouched(sender: UIButton)
{
delegate?.noteDetailViewControllerButtonTouched(self)
}
}
protocol NoteDetailViewControllerDelegate: class {
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController)
}
ListTableViewController.swift:
class ListTableViewController: UITableViewController, NoteDetailViewControllerDelegate
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
// Implement the delegate protocol
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController) {
// Do something! The button was pressed!
wordCount()
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.delegate = self
}
}
}
Just follow bellow code its very simple :-
NoteDetailViewController.swift:
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
var parentListTableView : ListTableViewController!
#IBAction func funcButTouched(sender: UIButton)
{
parentListTableView.wordCount()
}
}
ListTableViewController.swift:
class ListTableViewController: UITableViewController
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.parentListTableView = self
}
}
}

Pass data between ViewController and ContainerViewController

I'm working on an app, and need to pass data between view and containerView. I need to send data and receive data from both Views.
Let me explain better:
I can change the Label Master (Touch the Container Button) by protocol, but I can not change the Label Container (Touch the Master button). What happens is the Master connects with the container by a following. But do not have a follow Container linking to the Master.
I tried to add but segue to, but it worked.
The Master View Controller:
import UIKit
protocol MasterToContainer {
func changeLabel(text:String)
}
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
var masterToContainer:MasterToContainer?
#IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
let view = segue.destinationViewController as? Container
view!.containerToMaster = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button_Container(sender: AnyObject) {
masterToContainer?.changeLabel("Nice! It's work!")
}
func changeLabel(text: String) {
labelMaster.text = text
}
}
The Container View Controller:
import UIKit
protocol ContainerToMaster {
func changeLabel(text:String)
}
class Container: UIViewController, MasterToContainer {
var containerToMaster:ContainerToMaster?
#IBOutlet var labelContainer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button_Master(sender: AnyObject) {
containerToMaster?.changeLabel("Amazing! It's work!")
}
func changeLabel(text: String) {
labelContainer.text = text
}
}
Can someone help me?
All you need to do is keep a reference to Container in your master view controller.
That is, you should add an instance variable to Master that will hold a reference to the view controller, not just the view. You'll need to set it in prepareForSegue.
So the beginning of Master View Controller would look something like this:
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
var containerViewController: Container?
#IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
And then in your button function, simply change the label using the variable you just added.
Example:
#IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
This means you can get rid of your MasterToContainer protocol too.
I tested this code, so I know it works, but unfortunately I am an Objective-C dev, and know nothing about best practices in Swift. So I don't know if this is the best way to go about it, but it certainly works.
Edit:
Here's the exact code I've tested:
Master.swift:
import UIKit
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
#IBOutlet var labelMaster: UILabel!
var containerViewController: Container?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
#IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
func changeLabel(text: String) {
labelMaster.text = text
}
}
Container.swift:
import UIKit
protocol ContainerToMaster {
func changeLabel(text:String)
}
class Container: UIViewController {
#IBOutlet var labelContainer: UILabel!
var containerToMaster:ContainerToMaster?
#IBAction func button_Master(sender: AnyObject) {
containerToMaster?.changeLabel("Amazing! It's work!")
}
func changeLabel(text: String) {
labelContainer.text = text
}
}
I solved it with this code
To send data from ViewController -> ContainerViewController
Class ViewController : UIViewController {
func sendData(MyStringToSend : String) {
let CVC = childViewControllers.last as! ContainerViewController
CVC.ChangeLabel( MyStringToSend)
}
}
in your ContainerViewController
Class ContainerViewController : UIViewController {
#IBOutlet weak var myLabel: UILabel!
func ChangeLabel(labelToChange : String){
myLabel.text = labelToChange
}
}
To send data from ContainerViewController -> ViewController
Class ContainerViewController : UIViewController {
func sendDataToVc(myString : String) {
let Vc = parentViewController as! ViewController
Vc.dataFromContainer(myString)
}
}
and in ViewController
Class ViewController : UIViewController {
func dataFromContainer(containerData : String){
print(containerData)
}
}
I hope this will help someone.
you can use this extension to access the container child
extension UIViewController {
func getContainerChild<vc:UIViewController>(_ viewController : vc,_ hasNavigation : Bool = true) -> (vc) {
guard let vc = self.children[0] as? UINavigationController else {return viewController}
if hasNavigation {
guard let childVC = vc.children[0] as? PurchasedHistoryListVC else {
return viewController}
return childVC as! vc
} else {
return vc as! vc
}
}
}
so you can do some thing like this in your view Controller
let vc = self.getContainerChild(yourChildViewControllerClass())
vc.functionName()

Swift Take a Variable

I want to take a variable nomeLabel in the func goToSecondView and display it on a SecondView
class ViewController: UIViewController {
var nomeLabel:String!
#IBAction func goToSecondView(sender: AnyObject) {
self.performSegueWithIdentifier("goToSecondView", sender: self)
let newButton = sender as UIButton
nomeLabel = newButton.titleLabel!.text
println("nomeLabel \(nomeLabel)")
}
}
how can I do ?
You need to implement prepareForSegue where you have access to the destination view controller. Here is an example on how to do that:
class ViewController: UIViewController {
#IBOutlet var newButton: UIButton!
#IBAction func goToSecondView(sender: AnyObject) {
self.performSegueWithIdentifier("goToSecondView", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "goToSecondView" {
let secondViewController = segue.destinationViewController as SecondViewController
secondViewController.nomeLabel = self.newButton.titleLabel.text
}
}