Swift 3 - IBOutlet nil after copy UIView - ios

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")
}
}

Related

Protocol delegate is always nil

I am new to using xib files. So I'm not very clear on how they interact with their parent.
I have a custom view (LoginView) which is created from a xib file. This view also defines a protocol (LoginDelegate). The sole purpose of the delegate is to pass the username and password back to the caller.
I also have a ViewController (LoginVC) which implements this protocol. I am adding the LoginView to this VC.
I verified that I properly set the delegate in VC.viewDidLoad(). The problem is when I try to use the delegate to invoke the protocol method: the delegate is always nil. Somehow it is getting cleared. Here is the UIView:
// MARK:- Login Delegate method
// provide a means to pass the user credentials back to the parent
protocol LoginDelegate: AnyObject {
func getUsernameAndPassword(user: String, pwd: String)
}
class LoginView: UIView, UITextViewDelegate {
#IBOutlet weak var user: UITextView!
#IBOutlet weak var password: UITextView!
#IBOutlet weak var btnLogin: UIButton!
var userPlaceholderLabel : UILabel!
var pwdPlaceholderLabel : UILabel!
var view: UIView!
weak var loginDelegate: LoginDelegate?
// MARK:- internal use only
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
class func getInstance() -> LoginView {
let nib = UINib(nibName:"LoginView", bundle:nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! LoginView
return view
}
#IBAction func onLoginButtonPress(_ sender: Any) {
print ("\(#function): Username: \(user.text ?? ""), Password: \(password.text ?? "")")
self.loginDelegate?.getUsernameAndPassword(user: user.text, pwd: password.text )
}
// MARK:- TextView Delegate methods
func textViewDidChange(_ textView: UITextView) {...}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {...}
}
And the View Controller:
class LoginVC: UIViewController, LoginDelegate {
var isBleScan = true
#IBOutlet weak var btnToggleBleScan: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let loginSubview = LoginView.getInstance()
loginSubview.frame = self.view.bounds
loginSubview.loginDelegate = self
view.addSubview(loginSubview)
}
#IBAction func onToggleScanPressed(_ sender: Any) {
isBleScan = !isBleScan
if (isBleScan) {
btnToggleBleScan.setTitle("Stop Scan", for: UIControl.State.normal)
} else {
btnToggleBleScan.setTitle("Start Scan", for: UIControl.State.normal)
}
}
// MARK:- LoginDelegate methods
// METHOD IS NEVER CALLED - DELEGATE IS nil IN THE CALLER
func getUsernameAndPassword(user: String, pwd: String) {
let _user = user
let _pwd = pwd
print ("\(#function):Username: \(_user), Password: \(_pwd)")
}
}
The Connections for the Main Storyboard and Child xib, respectively:
I suspect I am not wiring things properly in IB, but I'm unsure. I have found many answers regarding this problem. I have tried many proposed solutions, but nothing works. I appreciate any input!
Create
class func getInstance() -> LoginView {
let nib = UINib(nibName:"LoginView", bundle:nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! LoginView
return view
}
then
let loginSubview = LoginView.getInstance()
loginSubview.frame = self.view.bounds
loginSubview.loginDelegate = self // I verified this sets the delegate to an instance of LoginVC
view.addSubview(loginSubview)
Then remove this function loadViewFromNib
current problem when you do LoginView() it creates an instance without it's layout and set the delegate for it but from your loadViewFromNib you create another instance with correct layout and add it to that view but this misses delegate assignment hence the top subview of the view you create in the vc has a nil delegate

Using UIView (Singleton) on different viewcontrollers

I have a UIView which have a button and some view to indicate sucess and failure. I am trying to use that UIView on other view controllers and receive the button action on called view controllers.
This is what i have tried so far
protocol FailViewDelegate: class {
func tryAgainTapped()
}
class AlertView: UIView {
static let instance = AlertView()
weak var delegate : FailViewDelegate?
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var messageLbl: UILabel!
#IBOutlet weak var dashIMageView: AnimatableImageView!
#IBOutlet weak var circleView: AnimatableView!
#IBOutlet weak var iconStatus: AnimatableImageView!
#IBOutlet weak var tryAgainButton: AnimatableButton!
#IBOutlet weak var parentView: UIView!
private override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("AlertView", owner: self, options: nil)
}
enum AlertType {
case success
case failure
}
func showAlert(alertType: AlertType, to: UIViewController) {
switch alertType {
case .success:
dashIMageView.image = UIImage(named: "circle-dash-blue")
circleView.backgroundColor = UIColor(hexString: "#4EBFFF")
titleLbl.text = "Success"
titleLbl.textColor = UIColor(hexString: "#4EBFFF")
messageLbl.text = "Your ticket has been created."
tryAgainButton.isHidden = true
iconStatus.image = UIImage(named: "icon-check")
case .failure:
dashIMageView.image = UIImage(named: "circle-dash-red")
circleView.backgroundColor = UIColor(hexString: "#EB3708")
titleLbl.text = "Failure"
titleLbl.textColor = UIColor(hexString: "#EB3708")
messageLbl.text = "There was an error, creating your ticket."
tryAgainButton.isHidden = false
iconStatus.image = UIImage(named: "icon-close")
}
parentView.center = to.view.center
to.view.addSubview(parentView)
}
func dismissAlert() {
parentView.removeFromSuperview()
}
#IBAction func tryAgainButtonTapped(_ sender: AnimatableButton) {
delegate?.tryAgainTapped()
}
}
This is how i have called the view
class CreateTicketViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
AlertView.sharedInstance.delegate = self
}
#IBAction func createTicketTapped(_ sender: AnimatableButton) {
AlertView.sharedInstance.showAlert(alertType: .failure, to: self)
}
}
extension CreateTicketViewController : FailViewDelegate {
func tryAgainTapped() {
print("Try Again Tapped")
}
}
This is the error that i got
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
(in dashIMageView.image = UIImage(named: "circle-dash-red")) and when i remove the dashImageView then the error occur for nextView and so on
You don't need to make it a singleton, which in this case (View) is a very uncommon approach, I think. You can create as many instances of that view as you want any time, anywhere (on other ViewControllers) you want and specify them, the way you want them to have.
When you want a view to be rendered and be visible, it always must be part of the view hierarchy in the current visible view controllers main view. And a view can only have ONE SUPERVIEW at the time, so whenever you add a (singleton) view to another superview, it will be removed from an other superview. If you want the same view on many view controllers (no problem), just don't let it be a singleton.
So first thing to do -> Remove the singleton design by commenting out that line:
class AlertView: UIView {
// make this line a comment or just remove it
// static let instance = AlertView()
weak var delegate : FailViewDelegate?
In your different view controllers you just create that instances of your AlertView and set the delegate correctly like this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myAlertView = AlertView()
myAlertView.delegate = self
// then you don't need this anymore
// AlertView.sharedInstance.delegate = self
}

Is it a good way to pass data to custom view then execute the function?

I created a custom input accessory view, it is the submit button.
However, I need to pass the data to the custom view then execute the further function. It is a good way to do that?
class SignUpViewController: UIViewController {
#IBOutlet weak var phoneTF: SignLogTextField!
#IBOutlet weak var EmailTF: SignLogTextField!
#IBOutlet weak var PasswordTF: SignLogTextField!
#IBOutlet weak var FBBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textFieldPreparation()
}
func textFieldPreparation(){
EmailTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
phoneTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
PasswordTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
}
}
I am not sure how to pass the data to the custom view or should I do the sign up in the Outlet Action?
It is my custom view
import UIKit
class SignSubmitBTN: UIView {
#IBAction func submitAction(_ sender: Any) {
}
#IBOutlet weak var subBTN: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){}
}
If I have to pass data to custom view should I use protocol? If I should use the protocol of how to use it?
OK...
I think you are approaching this from the wrong direction. The responsibility of a button should be to tell you that a user has tapped it and nothing more. The button should not be dealing with signing in.
But... you are 90% of the way there here. Just a few more bits to add.
You can update your submit button to include a delegate and use the delegate in your button action...
import UIKit
// protocol
protocol SignInButtonDelegate: class {
func signIn()
}
class SignSubmitBTN: UIView {
// property for delegate
weak var delegate: SignInButtonDelegate?
#IBAction func submitAction(_ sender: Any) {
// this tells the delegate to sign in
// it doesn't need to know how that happens
delegate?.signIn()
}
#IBOutlet weak var subBTN: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {}
}
Then in your view controller you conform to the delegate protocol...
extension SignUpViewController: SignInButtonDelegate {
func signIn() {
// here you already have access to all the data you need to sign in.
// you are in the view controller here so just get the text from the username, password, etc...
}
}
And then set the view controller as the delegate...
func textFieldPreparation() {
let signInButton = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
signInButton.delegate = self
// these are properties... they should begin with a lowercase letter
emailTF.inputAccessoryView = signInButton
phoneTF.inputAccessoryView = signInButton
passwordTF.inputAccessoryView = signInButton
}
Your CustomView is just a class at the end, so you can do it in object oriented paratime, For that write a function in your customView to pass data in it. Like
class SignSubmitBTN: UIView {
var data: String!;
public func setData(data: String) {
self.data = data;
}
/// Other code
}
And to set data after initializing your CustomView, call setData(params) function to set data in it.
Try this
func loadFromNib() -> SignSubmitBTN {
let bundle = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
return bundle
}
In your viewcontroller call like below:
let customObj = loadFromNib()
customObj.dataToGet = "Data to pass"
customObj.delegate = self
EmailTF.inputAccessoryView = customObj
If you want pass data from custom class, You need to use delegate protocol as #Fogmeister suggested.
If you want delegate option
public protocol menuOpen: class {
func openMenuAction(selectedValue : String)
}
class SignSubmitBTN: UIView {
open var delegate:menuOpen?
var dataToGet = ""
#IBAction func submitAction(_ sender: Any) {
self.delegate.openMenuAction("test")
}
}
Then add delegate method in your VC
class SignUpViewController: UIViewController,menuOpen{
func openMenuAction(selectedValue : String) {
//get your selected value here, you would better pass parameter in this method
}
}

how to use protocol delegate between nib class in swift?

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

Custom UIView is blank when instantiated from a XIB in Swift 4

I have a custom view that I am trying to load from a custom XIB, but the view appears to be blank when loaded, even thought it has the correct sizes when debugged.
My debug statements show that the frame has the correct sizes:
commonInit()
XIB: MyCustomView
myView Frame: (0.0, 0.0, 320.0,568.0)
myView ContentSize: (320.0, 710.0)
This is my custom VC that I am using to call my Custom View
class MyCustomViewController: UIViewController {
var myView : MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = MyCustomView(frame: self.view.frame)
self.view.addSubview(myView)
updateScrollViewSize()
print("myView Frame: \(myView.frame)")
print("myView ContentSize: \(myView.contentView.contentSize)")
}
func updateScrollViewSize () {
var contentRect = CGRect.zero
for view in myView.contentView.subviews {
contentRect = contentRect.union(view.frame)
}
myView.contentView.contentSize = CGSize(width: myView.contentView.frame.size.width, height: contentRect.size.height + 5)
}
}
There is a XIB that has the files owner as MyCustomView and all the outlets are hooked up correctly.
class MyCustomView: UIView {
let kCONTENT_XIB_NAME = "MyCustomView"
#IBOutlet var contentView: UIScrollView!
#IBOutlet weak var lbl_datein: UILabel!
//.. A bunch of other GUI elements for the scrollview
#IBOutlet weak var text_location: UITextField!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
print(#function)
print("XIB: \(kCONTENT_XIB_NAME)")
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.addSubview(self)
contentView.frame = self.bounds
contentView.backgroundColor = .blue
}
}
Does anyone see what I have done wrong when trying to load the view
I'm going to post an alternative to what you've done, using an extension.
extension UIView {
#discardableResult
func fromNib<T : UIView>(_ nibName: String? = nil) -> T? {
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed(nibName ?? String(describing: type(of: self)), owner: self, options: nil)?[0] as? T else {
return nil
}
view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
view.autoPinEdgesToSuperviewEdges()
return view
}
}
*Note that I am using PureLayout for convenient autolayout management, you could just apply the constraints manually yourself though if you aren't using PureLayout.
Using the above all you have to do is call the below from your init;
fromNib()
*Final note. The custom view name must match the nib name, otherwise you must pass the nib name in to you fromNib function.
You now have something much more reusable.
If my alternative answer is too much, let me try solve your existing issue. Instead of the below;
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.addSubview(self)
Try;
let nibView = Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
self.addSubView(nibView)
I couldn't get this to run copy/pasting the code. Maybe there's some setup missing, but I'm having a hard time understanding how it's supposed to work. The original code in the question crashes on this line:
contentView.addSubview(self)
because when you have IBOutlets, they will always be nil if you initialize it using MyCustomView(frame: self.view.frame). It has to call the initWithCoder function.
There's a lot going on here, but this is how I would do it:
class MyCustomViewController: UIViewController {
var myView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)?.first as? MyCustomView
self.view.addSubview(myView)
updateScrollViewSize()
print("myView Frame: \(myView.frame)")
print("myView ContentSize: \(myView.contentView.contentSize)")
}
func updateScrollViewSize () {
var contentRect = CGRect.zero
for view in myView.contentView.subviews {
contentRect = contentRect.union(view.frame)
}
myView.contentView.contentSize = CGSize(width: myView.contentView.frame.size.width, height: contentRect.size.height + 5)
}
}
class MyCustomView: UIView {
let kCONTENT_XIB_NAME = "MyCustomView"
#IBOutlet var contentView: UIScrollView!
#IBOutlet weak var lbl_datein: UILabel!
//.. A bunch of other GUI elements for the scrollview
#IBOutlet weak var text_location: UITextField!
}
I'm assuming that the top-level object in the nib is of class MyCustomView, which is going to lead to a lot of weird things. loadNibNamed will call init?(coder aDecoder: NSCoder), so ideally you'd just be calling that from your view controller in the first place, instead of from the custom view object.
With regards to the "can't add self as subview" error, I did not see that error while running, but I would expect it from this line:
contentView.addSubview(self)
since that's exactly what it does, add self as a subview of a view that's already a subview of self.

Resources