I am trying to delegate a function with which I will call the new ViewController. In run, it gets this error:
setValue: forUndefinedKey:]: this class is not key value coding-compliant for the key mapView. '
What am I doing wrong or how can I fix it? I will be grateful for all the tips.
import UIKit
import MapKit
ViewController.swift:
class ViewController: UIViewController, CustomViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName:"Maps", bundle:nil)
let MapsView = nib.instantiate(withOwner: self, options: nil).first as! Maps
MapsView.delegate = self
}
func showWaypointDetails() {
print("show")
}
Maps.swift:
protocol CustomViewDelegate: class {
func showWaypointDetails()
}
class GpsMaps: UIView, MKMapViewDelegate {
#IBOutlet var view: UIView!
#IBOutlet weak var mapView: MKMapView!
weak var delegate: CustomViewDelegate?
}
It looks like you are either not instantiating the right view or the view is not set up correctly.
In your Maps.xib file:
Check if the "File's Owner" points to your Maps.swift class
Check if you have a MKMapView outlet named "mapView"
You are casting the nib (which I assume contains a MKMapView) as Maps, which is just a simple protocol. In doing this you will lose all mapView functionality as the app won't know it's a MapView.
You need to load it and cast it as the MapView Class you are using, and then adopt the protocol in your MapView class...
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName:"Maps", bundle:nil)
let mapsView = nib.instantiate(withOwner: self, options: nil).first as! MyMapViewClass
mapsView.delegate = self
}
extension MyMapViewClass: Maps {
// implement showWaypointDetails() or implement it as a default in a protocol extenstion
}
Related
I have added a protocol that makes a segue from a UIView button to a NavigationController but when i compile and press the button after the UIView opens it just does nothing which seems pretty weird since it all makes sense to me at least, Please if I am doing anything wrong lead me to fix my code.
I got this code from this Answer: https://stackoverflow.com/a/52789290/18277261
Code:
class popUpView: UIView, UITextFieldDelegate {
#IBOutlet weak var popUpViewInterface: UIView!
#IBOutlet weak var payNowPopUp: UIButton!
let delegate: popUpViewDelegate? = nil
#IBAction func payNowToWebView(_ sender: Any) {
let myColor = UIColor.red
priceTextFieldPopUp.layer.borderColor = myColor.cgColor
priceTextFieldPopUp.layer.borderWidth = 1.0
self.delegate?.performSegueFromView(withIdentifier: "paymentWebView")
}
}
protocol popUpViewDelegate {
func performSegueFromView(withIdentifier identifier: String)
}
class dashboardViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource, ChartViewDelegate, AxisValueFormatter, popUpViewDelegate {
func performSegueFromView(withIdentifier identifier: String) {
self.performSegue(withIdentifier: identifier, sender: nil)
}
In dashboardViewController where you initialise popUpView make sure that you set the delegate like below.
let popView = popUpView()
popView.delegate = self
This question deals with the same code with my previous question.
(The previous question is Why is this View nil in awakeFromNib()?)
But totally different approach.
It's not the duplicated question.
So let me restructure my question.
First, let me summarize my project.
Thanks to previous answer, I can reuse xib now.
This is CardContentView.xib
Codes are below
#IBDesignable class CardContentView: UIView {
#IBOutlet weak var backgroundView: UIView!
#IBInspectable var nibName:String?
var contentView: UIView?
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
func xibSetup(){
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
guard let nibName = nibName else { return nil }
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
And this is CardView.xib, the property follows 'class CardContentView'
Codes are below
class CardView: UIView {
#IBOutlet weak var cardContentView: CardContentView!
override func awakeFromNib() {
cardContentView.layer.cornerRadius = 16
}
}
This is Main.storyboard. with a single UIView.
Codes are below
class ViewController: UIViewController {
#IBOutlet weak var cardView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
But, I want to reuse class 'CardView' to 'cardView' property in ViewController.
I want to get 'background' property to 'cardView' property (In simple words, I want to change the yellow box to the blue box in CardContentView!).
And I want to make 'cardView' property with a transition effect.
But, I cannot make extension because it's not in a UIView class, but in a ViewController class.
So I need to make another UIView class.
That's why I made a CardView class separately.
(Of course, I want to organize codes neatly by making another file.)
So, I tried 2 different ways.
Set the property 'cardView' class as CardView, and connect it to ViewController.
class ViewController: UIViewController {
#IBOutlet weak var cardView: CardView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Cast 'CardView' class to other property in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let tappedView = self.cardView as! CardView
self.view.addSubview(tappedView)
}
But both of them make errors.
1. When I made '#IBOutlet weak var cardView: CardView!', it throws error message, 'Unexpectedly found nil while unwrapping an Optional value, cardContentView = nil'.
2. When I made 'let tappedView = self.cardView as! CardView', it throws error message, 'Could not cast value of type 'UIView' (0x10a229ff8) to 'AwakeFromNibTutorial.CardView' (0x1060aa8d0).'
How can I solve this problem?
Please help me!
In the CardView class's awakeFromNib(), you are not calling super.awakeFromNib().
You would have to use the same logic (xibSetup()) that you used to load the CardContentView from it's corresponding XIB, to load the CardView also from its XIB.
In the ViewController in your storyboard, have you assigned the class of the UIView to CardView?
I'm working on a Swift app and running into a crazy odd problem. I have a simple storyboard view controller setup, it has 3 UIButtons a UIImageView and a UIView (as a subview of the main view).
I want to programmatically add a WKWebView to the UIView.
Easy enough right? All of the above are in the Storyboard View, declared in the custom UIViewController class and connected in IB. However at run time, everything is nil. This is a snippet of my code:
#IBOutlet var button1 : UIButton!;
#IBOutlet var button2 : UIButton!;
#IBOutlet var button3 : UIButton!;
#IBOutlet weak var containerForWebView: UIView!
var webView: WKWebView!
override func loadView()
{
}
override func viewDidLoad()
{
super.viewDidLoad()
displayWebPage()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
}
private func displayWebPage()
{
webView = WKWebView()
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.frame = CGRect(origin: CGPoint.zero, size: containerForWebView.frame.size)
webView.navigationDelegate = self
containerForWebView.addSubview(webView)
let url = URL(string: "https://www.google.com")!
webView.load(URLRequest(url: url))
}
When the code calls the displayWebPage() method I break on the first line. In the debugger you can see all of the UIViewController properties are nil. The IBOutlets are Nil and the _view variable of the UIViewController itself is nil.
I don't understand why this is happening. Everything is pretty simple and easily connected. I never ran into this sort of issue in Objective-C. Any advice is appreciated!
Remove loadView implementation. Do not override that method, always use viewDidLoad instead.
If you override loadView then you are responsible for creating controller's view and assigning it to self.view.
I am doing a Mac application, and I have a problem appending text to a NSScrollView when I call a function from a different class.
I have this function on my ViewController class:
import Cocoa
class PopoverVC1: NSViewController {
let popover1 = NSPopover()
class func loadView() ->PopoverVC1 {
let vc = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),
bundle: nil).instantiateController(withIdentifier:
NSStoryboard.SceneIdentifier(rawValue: "Popover1")) as! PopoverVC1
vc.popover1.contentViewController = vc
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
popover1.behavior = .transient
popover1.contentViewController = self
}
func showPopover (view: NSView){
popover1.show(relativeTo: view.bounds, of: view, preferredEdge: .maxY)
}
#IBOutlet weak var radioOption1: NSButton!
#IBOutlet weak var radioOption2: NSButton!
#IBOutlet weak var radioOption3: NSButton!
#IBAction func clickOption(_ sender: NSButton) {
switch sender {
case radioOption1: popover1.performClose(sender)
case radioOption2: let vc = ViewController()
vc.myPrint(string: "This is a test")
default: print ("hello")
}
}
}
Than I have a PopoverVC1 class, which is a class to a popover I am using:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var oneYes: NSButton!
#IBOutlet weak var oneNo: NSButton!
#IBOutlet weak var notesArea: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded
}
}
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
let popover1 = NSPopover()
#IBAction func oneClicked(_ sender: NSButton) {
switch sender {
case oneYes: let vc = PopoverVC1.loadView()
vc.showPopover(view: sender)
case oneNo:
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: "test")
text?.append(attr)
default: print ("")
}
}
}
However, I got an error when I press the radio button "oneNo" that should call the function "myPrint" and pass the argument.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I did some tests and when I call this same function "myPrint" from within the ViewCotroller class it works fine.
Any ideas?
Your issue is in clickOption when you are calling:
let vc = ViewController()
vc.myPrint(string: "This is a test")
When you call this method from code and the ViewController's UIViews are set up in a storyboard, the connection from the storyboard is not made. That is why the notesArea is nil when you call the function myPrint. In this case you are creating a new copy of ViewController and it will not be the same one that created the popover.
There are a few ways you can solve the problem that you are trying to accomplish. One of them is known as a delegate. This is a way for you to to call the ViewController's methods like your popover inherited them. You can check out a tutorial here. The idea is that we want to have a reference to the ViewController in your popover so that you can call the functions in the protocol. Then the ViewController that conforms to the protocol will be responsible for handling the method call.
So let's create a protocol called PrintableDelegate and have your ViewController class conform to it. Then in your popover, you will be able to have a reference to the ViewController as a weak var called delegate (you can use what ever name you want but delegate is standard). Then we can call the methods described in the protocol PrintableDelegate, by simply writing delegate?.myPrint(string: "Test"). I have removed some of your irrelevant code from my example.
protocol PrintableDelegate {
func myPrint(string: String)
}
class ViewController : UIViewController, PrintableDelegate {
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
#IBAction func oneClicked(_ sender: NSButton) {
let vc = PopoverVC1.loadView()
// Set the delegate of the popover to this ViewController
vc.delegate = self
vc.showPopover(view: sender)
}
}
class PopoverVC1: NSViewController {
// Delegates should be weak to avoid a retain cycle
weak var delegate: PrintableDelegate?
#IBAction func clickOption(_ sender: NSButton) {
// Use the delegate that was set by the ViewController
// Note that it is optional so if it was not set, then this will do nothing
delegate?.myPrint(string: "This is a test")
}
}
I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!