i have one view controller that it has two sub view of nib files.
my first nib name is citycollection and secend is currentxib that both of them are UIVIEW.
in citycollection view i have collectionview that i want when i click on item of that, print the data i send by protocol in the label of currentxib class.( attention both of them are UIView and not view controller.) but it not work.
my main view controller class is empty still.
this is my code:
CityCollection class:
///// CityCollection
class CityCollection: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
weak var delegate: sendDataDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let fName = Publics.instance.getCities()[indexPath.row].fName
delegate?.name(data: fName)
}
}
protocol sendDataDelegate : NSObjectProtocol {
func name(data : String)
}
CurrentXib class :
////CurrentXib
class CurrentXib: UIView, sendDataDelegate {
func name(data: String) {
lblCityName.text = data
}
#IBOutlet weak public var lblCityName: UILabel!
override func awakeFromNib() {
let myCity = CityCollection()
myCity.delegate = self
}
}
what should i do?
The problem is here:
let myCity = CityCollection() // <-- this is the problem
myCity.delegate = self
All you do here is create a new instance of the CityCollection class. You set that instance's delegate in the next line. And then... myCity, your CityCollection object, vanishes in a puff of smoke. So those two lines are useless.
What you probably meant to do was to obtain, somehow, a reference to an aleady existing CityCollection object that is present somewhere else in your interface.
From ViewController, which is now empty, assuming ParentViewController. In this view controller , you are holding two nibs
In viewDidLoad of the parent view controller, you add your collectionView.delegate = self. and implement your delegate method
extension ParentViewController : sendDataDelegate {
func name(data : String) {
print(data) // you have got your data here
}
}
So far, you have a parentViewController , with two views, one of collection view, didSelect in one item, and parentviewController knows which name is selected. Now you have to send Data to CurrentXib.
To do so, you may do notification
extension ParentViewController : sendDataDelegate {
func name(data : String) {
print(data) // you have got your data here
let dataDict = ["name" : data]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo:dataDict)
}
}
you have to listen the notification, add lines awakefromNib of CurrentXIB,
NotificationCenter.default.addObserver(self, selector: #selector(self.method(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
now you have to add this metods
func method(_ notification: NSNotification) {
if let dict = notification.userInfo as? [String : String] {
if let name = dict["name"] as? String{
//currentXIB got the name right now, he has to update now
lblCityName.text = name
}
}
}
i found the solution .
first i clean below code from awakeFromNib :
let myCity = CityCollection()
myCity.delegate = self
then i add outlets of subviews in my main viewcontroller class:
#IBOutlet weak var cityCollection: CityCollection!
#IBOutlet weak var currentXib: CurrentXib!
then i write below code in viewDidLoad:
self.cityCollection.delegate = self.currentXib
you can take share instance of CityCollection in CityCollection.class like this
static let shared = CityCollection()
After that in your CurrentXib you can use with this
CityCollection.shared.delegate = self
Related
I have a viewController with another containerView insider set up to appear temporarily (added programmatically). The containerView is a sort of operation bar, which allows you to change values of the viewController. The protocol called from an IBAction of a button however, does not call the protocol set up inside the viewController class.
Here is the code from both classes:
class viewController: UIViewController, updateListDelegate {
let dataSource = containerView()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
func updateList(sender: containerView) {
print("is called") //is not printed
}
}
The code from the containerView:
protocol updateListDelegate {
func updateList(containerView)
}
class containerView: UIViewController {
var delegate: updateListDelegate?
#IBAction func AddSong(_ sender: UIButton) {
self.delegate?.updateList(sender: self)
}
}
If this method is only to be called from one object, then, in my opinion, I would not define a protocol. If multiple objects are to call this method, then I would define a protocol. This is typically how you would call a method backwards, using a basic delegate.
class ViewController: UIViewController {
let container = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
container.viewControllerDelegate = self
// push to this instance of container at some point
}
func doSomething() {
print("great success")
}
}
class ContainerView: UIViewController {
weak var viewControllerDelegate: ViewController?
#objc func someAction() {
if let viewControllerDelegate = viewControllerDelegate {
viewControllerDelegate.doSomething()
}
}
}
// prints "great success" when someAction() called
One of the most common mistakes people make is not keeping track of instances. For delegates to work, you must be sure you are using the specific instances that you've instantiated and assigned those delegates to.
i have a question about a more or less special case. I have to copy a UIView which is loaded from nib. This is the initaliziation of the source variable:
let view = Bundle.loadView(fromNib: "MyView", withType: MyView.self)
The view has two labels as outlets properties like so:
class MyView: UIView {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var subLabel: UILabel!
}
In my case I have to copy this view. So I found this solution which should work fine:
import UIKit
extension UIView
{
func copyView<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
}
Unfortunately when I call this line:
let copyView = view.copyView()
The label and subLabel properties are nil. In view they are set. The FilesOwner in the MyView.xib is set to the MyView class
Could the copy function work in my case? Has someone an advice how to proceed here?
Get UIView using below method
let view = MyView(nibName: "MyView", bundle: nil)
Hope it help.
To be honest there is still something strange about your use case although I don´t know your whole code but I don´t see the point in using the NSKeyedArchiver for what you want to achieve. Of course it is possible to instantiate a new UIView instance and still take advantage of polymorphism. Here´s how:
Imagine you have the following extension to instantiate a generic view controller:
import UIKit
extension UIView {
class func fromNib(owner: AnyObject? = nil) -> Self {
return UIView.fromNib(owner: owner)
}
class func fromNib<T : UIView>(owner: AnyObject? = nil) -> T {
return UIView.fromNib(withName: T.className, owner: owner) as! T
}
class func fromNib(withName name: String, owner: AnyObject? = nil) -> UIView {
return Bundle.main.loadNibNamed(name, owner: owner, options: nil)![0] as! UIView
}
}
And now you add another extension to UIView to return another view of exact the same type:
extension UIView {
func getCopy() -> Self {
return UIView.fromNib()
}
}
You can even override this method in your subclasses to pass custom variables:
class MySubView: AnyParentView {
var testVariable: Int?
override func getCopy() -> MySubView {
let view = MySubView.fromNib()
view.testVariable = self.testVariable
return view
}
}
Now you can easily instantiate views and copy them while keeping their respective subtype. If the outlets are set correctly in the xib they will also be set for the new "copied" view instance. You can then pass it to your method that expects a UIView subclass.
Hope this helps!
Maybe the following might work? Have a nice day.
import Foundation
import UIKit
class MyView: UIView {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var subLabel: UILabel!
required convenience init?(coder aDecoder: NSCoder) {
self.init()
self.label = aDecoder.decodeObject(forKey: "label") as? UILabel
self.subLabel = aDecoder.decodeObject(forKey: "subLabel") as? UILabel
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(self.label, forKey: "label")
aCoder.encode(self.subLabel, forKey: "subLabel")
}
}
I'm trying to use KVO to observe the update change when using drag in my page view controller's child content view controller's scrollView, but when the app launch, it crashed says:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7ff003d3f5b0 of class KVOPageVC.ContentViewController was deallocated while key value observers were still registered with it."
Here below is my code and screenshot:
APP SCREENSHOT
CODE
PageViewController.swift
import UIKit
class PageViewController: UIPageViewController {
var pageLabels: Array<String> = ["First Page", "Second Page", "Third Page"]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
self.setViewControllers([contentViewForPage(0)], direction: .Forward, animated: true, completion: nil)
}
func contentViewForPage(index: Int) -> ContentViewController {
let contentVC = storyboard!.instantiateViewControllerWithIdentifier("ContentVC") as! ContentViewController
contentVC.pageIndex = index
contentVC.label = pageLabels[index]
return contentVC
}
}
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == 0 || index == NSNotFound {
return nil
}
index -= 1
return contentViewForPage(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == NSNotFound {
return nil
}
index += 1
if index == self.pageLabels.count {
return nil
}
return contentViewForPage(index)
}
}
ObeserverViewController.swift
it's 'a view controller' embedded in 'content view controller's' 'Container View', when the user drag and release the scroll below, i want the emoji face to be replaced by the text "to be notified!"
import UIKit
class ObeserverViewController: UIViewController {
#IBOutlet weak var notifyLabel: UILabel!// when the user drag the scroll view and release, i hope its value will be changed accordingly.
var contentVC: ContentViewController! //the variable to hold the object
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.contentVC = self.storyboard!.instantiateViewControllerWithIdentifier("ContentVC")
self.contentVC.addObserver(self, forKeyPath: "changingLabel", options: [], context: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "changingLabel" {
notifyLabel.text = "to be notified!"
}
}
deinit {
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
}
ContentViewController.swift
Page view controller's child view controllers, 3 pages in total. It includes a Scroll View and a Container View(embedded ObeserverViewController)
import UIKit
class ContentViewController: UIViewController {
var label: String!
var pageIndex: Int!
dynamic var changingLabel: String = ""
#IBOutlet weak var contentLabel: UILabel!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel.text = label
self.scrollView.delegate = self
self.contentView.backgroundColor = UIColor.greenColor()
}
}
extension ContentViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -50 {
if contentView.backgroundColor == UIColor.greenColor() {
contentView.backgroundColor = UIColor.yellowColor()
self.setValue("hah", forKey: "changingLabel")
} else {
contentView.backgroundColor = UIColor.greenColor()
self.setValue("wow", forKey: "changingLabel")
}
}
}
}
My questions is:
How can i make the emoji label text to be notified to change in one controller when i drag and release the scroll view in another controller?
Thank you very much in advance!
Firstly you are adding observer to one object and removing from another. When you are calling instanciateViewController... it will create you new object of passed view controller identifier. Than you are signing to it's changes via KVO. But in viewWillDissapear you are not getting the same object that was crated in viewWillAppear, but crating new one(it has nothing with that was created in viewWillAppear). Than you are resigning from it's notification, still as it is not the same object that you has created previously and signed to him(with KVO), such resigning won't lead to needed result. What you need to do is to save firstly created object to some variable and than resign this variable where it's needed.
Secondly you need to remove observer not only in viewWillDissapear method but as well in
deinit {
// perform the deinitialization
}
Than you will be sure that if your object is deleted than it as well will be resigned from notifications.
Your error message tells that object that was signed for notifications was marked as deleted, still it was not signed out and it will be receiving notification even if it's deleted(sound not really good as memory that he belongs to may already be used for some other objects, that may lead to undefined behavior of your app).
I present my secondViewController from (attendanceViewController) and in dismiss completion I'm trying to pass parameters and call functions. The AttendanceViewController appears and the function is called. The problem is that all the Objects are nil when dismiss(#IBOutlet weak var tableView: UITableView! , #IBOutlet weak var boxTypeSKU: UIView!....all)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ i
let attView: AttendanceViewController = self.storyboard!.instantiateViewControllerWithIdentifier("AttendanceViewID") as! AttendanceViewController
attView.currAttendance = self.currAttendance
attView.searchProductWithSKU("\(sku)")
})
I solved my problem using Protocols like this tutorial (http://swiftdeveloperblog.com/pass-information-back-to-the-previous-view-controller/) I think it's more elegant and efficient.
There's my updated code:
In second view Controller (BarcodeScannerViewController.swift) I do it:
protocol BarcodeScannerProtocol {
func setSKUScanner(sku: String)
}
class BarcodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var delegate:BarcodeScannerProtocol?
func back() {
let sku = (barcode as NSString).substringWithRange(NSMakeRange(6, 8))
delegate?.setSKUScanner(sku)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ in
}
}
In first view controller (AttendanceViewController.swift):
class AttendanceViewController: UIViewController, BarcodeScannerProtocol {
var strSKUScanner : String?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let skuScanned = strSKUScanner {
searchProductWithSKU(skuScanned)
} else {
fetchProducts()
}
}
// MARK: BarcodeScannerProtocol functions
func setSKUScanner(sku: String) {
self.strSKUScanner = sku
}
}
The first thing to be noticed is that a new instance of AttendanceViewController is being instantiated. This means that the properties are not being set on the correct object. There needs to be a reference to the view controller that presented the secondViewController. How that is done is up to you, but I recommend a callback containing the currAttendance variable. This would be a property on the presented view controller. Once the callback is called by the presented view controller, the parent AttendanceViewController can set its own property and dismiss the presented view controller and call the searchProductWithSKU(_:) method.
I want to update the label in my DashboardViewController from my AccountViewController when the back button is pressed in AccountViewController.
I have tried passing back a variable from 2nd view to 1st view and updating the label in viewDidLoad and in viewWillAppear but it never updates the label when the 1st view is back on screen.
I tried creating a function in 1st view to update the label with a string passed into the function and calling that function from 2nd view but it says that the label is nil so it couldn't be updated.
My latest attempt was to create a delegate but that didn't work either.
Here is my delegate attempt.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
override func viewDidLoad() {
super.viewDidLoad()
AccountViewController.delegate = self
}
}
And then in my AccountViewController I have this
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var info_changed = false
static var delegate: AccountViewControllerDelegate!
#IBAction func back_btn(sender: AnyObject) {
if(info_changed){
AccountViewController.delegate.name_changed(name_tf.text!)
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Did I mess up the delegate process somehow ? Or is there an easier way to do this?
First. Your delegate should be a normal property of AccountViewController. There is no need to update your name when user press back. You can change DashboardViewController`s name when user change name in AccountViewController. When user go back to DashboardViewController. It`s already show the changed name.
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var delegate: AccountViewControllerDelegate?
// when user change name through textfield or other control
func changeName(name: String) {
delegate?.name_changed(name)
}
}
Second. When DashboardViewController show AccountViewController. I think it should be push. Set DashboardViewController instance be AccountViewController instance`s delegate.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
// present or push to AccountViewController
func showAccountViewController {
let accountViewController = AccountViewController()
accountViewController.delegate = self
// do push view controller
}
}