Why my scrollView is nil? - xcode

How can be possible that the scrollview is nil when using the first:#IBAction afegeixGrafic but when using #IBAction func button it isn't'?
I got the class GraficsBalancGlobalViewController which is subclass of the class GraficViewController
class GraficsBalancGlobalViewController: GraficViewController {
#IBAction func afegeixGrafic(sender: NSButton) {
addNewGrafic() // which is set on the GraficViewController
}
}
And when I perform the IBAction afegeixGrafic my program crashes on the line marked below:
class GraficViewController: NSViewController, GraficViewDataSource {
#IBAction func button(sender: NSButton) {
addNewGrafic()
}
func addNewGrafic() {
let frame = NSRect(x: 0, y: 0, width: self.view.bounds.width , height: self.view.bounds.width * 0.25)
let nouGrafic = GraficView(frame: frame)
scrollView.addSubview(nouGrafic) <---- BREAK here!
}
#IBOutlet weak var scrollView: NSView!
//...more code
}
The compiler says that:
fatal error: unexpectedly found nil while unwrapping an Optional value
but the button (IBAction) inside the GraficViewController works well!! So i suppose that the problem is related with the scrollView, but I have no idea of what can be.. It is initialized..
I try this too:
#IBOutlet weak var scrollView: NSView? {
didSet {
guard let sview = scrollView else {
addNewGrafic()
return // because scrollView is nil for some reason, but don't work
}
}
}
Full code:
class GraficsBalancGlobalViewController: GraficViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
#IBAction func afegeixGrafic(sender: NSButton) {
//performSegueWithIdentifier("Nou Grafic", sender: nil)
//let viewController = storyboard!.instantiateControllerWithIdentifier(identifier!) as! GraficsBalancGlobalViewController
addNewGrafic()
}
#IBAction func eliminaGrafic(sender: NSButton) {
}
}

Its nil because of how you instantiated GraficsBalancGlobalViewController. You set it up with a xib or a storyboard however you instantiated it without one.
Do this:
let viewController = storyboard.instantiateControllerWithIdentifier(identifier) as GraficsBalancGlobalViewController
Not This:
let viewController = GraficsBalancGlobalViewController()

Related

swift 4- Save the last data of a text field in a label so that they are displayed when the app is restarted

I have a problem, I want to create a small app in which data in a formula can be charged.
Currently the data from three ViewControllers and one PickerViewController will be given back to the first ViewController.
That works very well too.
Now I want that the data at the start not on "nil" set but have a certain value.
Thereafter, the data entered last should reappear when the app is restarted.
I would like to apologize for my english, it is not my preferred language ...
Here is a part of my code how I wrote it
Main ViewController:
import UIKit
class RecivingViewController: UIViewController, SendDataBack, SendDataBack2, SendDataBack3, SendDataBack4 {
#IBOutlet weak var recivingData: UILabel!
#IBOutlet weak var recivingData2: UILabel!
#IBOutlet weak var recivingData3: UILabel!
#IBOutlet weak var recivingData4: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func userData(data: String) {
recivingData.text = data
}
func userData2(data: String) {
recivingData2.text = data
}
func userData3(data: String) {
recivingData3.text = data
}
func PickerData(data: String){
recivingData4.text = data
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "view1" {
let SendingVC: SendingViewController = segue.destination as! SendingViewController
SendingVC.delegate = self
}
if segue.identifier == "view2" {
let SendingVC2: Sending2ViewController = segue.destination as! Sending2ViewController
SendingVC2.delegate = self
}
if segue.identifier == "view3" {
let SendingVC3: Sending3ViewController = segue.destination as! Sending3ViewController
SendingVC3.delegate = self
}
if segue.identifier == "picker" {
let SendingVC4: PickerViewController = segue.destination as! PickerViewController
SendingVC4.delegate = self
}
}
}
one of the other ViewControllers:
import UIKit
protocol SendDataBack {
func userData(data: String)
}
class SendingViewController: UIViewController {
#IBOutlet weak var DataTxt: UITextField!
var delegate: SendDataBack? = nil
#IBAction func done(_ sender: Any) {
if delegate != nil {
if DataTxt.text != nil {
let data = DataTxt.text
delegate?.userData(data: data!)
dismiss(animated: true, completion: nil)
}
}
}
In order to see data after app is restarted you should use user defaults.
For saving data
UserDefaults.standard.set(newValue, forKey: "data")
For loading data in your view controller, if it's first load, when data is nil
UserDefaults.standard.string(forKey: "data")

How to access variable from one class in another? Swift Code

I am having an issue getting one simple variable from one class to another and it is beyond extremely frustrating :/...
Here is the deal I have two view controller classes named: ViewController.swift and ViewController2.swift
ViewController.swift is linked to a storyboard that has sort of an inventory bag that I have placed which is just an image. Then there is an #IBAction for when you click on the bag it opens up and the second storyboard pops into view. This is controlled by ViewController2.swift. All I am looking to do is simply pass the center of the bag image from ViewController to ViewController2 but I can't seem to get it to work any help would be greatly appreciated.
class ViewController: UIViewController {
#IBOutlet weak var InventoryBag: UIImageView!
var bagCenter:CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad() {
super.viewDidLoad()
#IBAction func InventoryButtonTapped(sender: UIButton) {
self.InventoryBag.image = UIImage(named: "backpackimageopen")
bagCenter = self.InventoryBag.center
func transferViewControllerVariables() -> (CGPoint){
return bagCenter
}
When I print the bagCenter from this ViewController it works properly and gives me a correct value. So the bagCenter variable I would like to somehow pass over to ViewController2.swift.
Here is what I tried from ViewController2.swift but it never seems to work and always gives me a 0 rather than the actual value.
class ViewController2: UIViewController {
#IBOutlet weak var HideButton: UIButton!
#IBOutlet weak var InventoryCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Load the center of the inventory bag to this view controller to align the hide button.
var bagCenter = ViewController().transferViewControllerVariables()
}
But when I do this it always results in a 0 and I don't get the actual coords of the bag that are showing up in ViewController1.
Create a following variable in ViewController2
var previousViewController: ViewController!
Add following line of code in your ViewController Class
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if segue.destinationViewController .isKindOfClass(ViewController2){
let vc2 = segue.destinationViewController as! ViewController2
vc2.previousViewController = self
}
}
Now in viewDidLoad method of ViewController2 you can access bagCenter like below:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var bagCenter = previousViewController.transferViewControllerVariables()
}
Try this in view Controller
class ViewController: UIViewController {
#IBOutlet weak var InventoryBag: UIImageView!
var bagCenter:CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func InventoryButtonTapped(sender: AnyObject) {
self.InventoryBag.image = UIImage(named:"MAKEUP_SHARE.jpg")
bagCenter = self.InventoryBag.center
performSegueWithIdentifier("Go", sender: self)
}
// Give a segue identifier in storyboard
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yoursegueidentifier" {
let dvc = segue.destinationViewController as? ViewController2
dvc!.bagCenter = bagCenter
}
}
}
and in view controller2
class ViewController2: UIViewController {
var bagCenter:CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
print(bagCenter)
}
}

Creating a Scroll View Protocol in swift 2.2

I am currently developing an iOS application with login and sign up forms. To make sure that the keyboard does not cover any UITextFields I've implemented the following solution provided by Apple and discussed in this issue.
To briefly sum it up, this solution uses a UIScrollView in which the different UI elements are placed and UIKeyboardDidShowNotification and UIKeyboardDidHideNotification to move the elements up and down when the keyboard appears/disappears so that the UITextFields aren't hidden.
This works like a charm except for one thing: for all my UIViewControllers I have to repeat the same code. To tackle my problem I have tried:
to create a base UIViewController, providing an implementation for the different functions, that can be subclasses by the other UIViewControllers;
to use a protocol and a protocol extension to provide a default implementation for the different functions and make my UIViewControllers conform to it.
Both solutions didn't solve my problem. For the first solution, I wasn't able to connect the UIScrollView of my base class through the Interface Builder although it was declared.
#IBOutlet weak var scrollView: UIScrollView!
When trying to implement the second solution, the UIViewController implementing my protocol somehow did not recognise the declared methods and their implementations.
The protocol declaration:
protocol ScrollViewProtocol {
var scrollView: UIScrollView! { get set }
var activeTextField: UITextField? { get set }
func addTapGestureRecognizer()
func singleTapGestureCaptured()
func registerForKeyboardNotifications()
func deregisterForKeyboardNotifications()
func keyboardWasShown(notification: NSNotification)
func keyboardWillBeHidden(notification: NSNotification)
func setActiveTextField(textField: UITextField)
func unsetActiveTextField()
}
The protocol extension implements all functions expect for the addTapGestureRecognizer() as I would like to avoid using #objc:
extension ScrollViewProtocol where Self: UIViewController {
// The implementation for the different functions
// as described in the provided links expect for the following method
func registerFromKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil, usingBlock: { notification in
self.keyboardWasShown(notification)
})
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidHideNotification, object: nil, queue: nil, usingBlock: { notification in
self.keyboardWillBeHidden(notification)
})
}
}
Does anyone have a good solution to my problem, knowingly how could I avoid repeating the code related to moving the UITextFields up and down when the keyboard appears/disappears? Or does anyone know why my solutions did not work?
I found a solution. I'll post it in case someone once to do the same thing.
So, I ended up deleting the UIScrollView outlet in my base class and replacing it with a simple property that I set in my inheriting classes. The code for my base class look as follow:
import UIKit
class ScrollViewController: UIViewController, UITextFieldDelegate {
// MARK: Properties
var scrollView: UIScrollView!
var activeTextField: UITextField?
// MARK: View cycle
override func viewDidLoad() {
super.viewDidLoad()
let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapGestureCaptured))
scrollView.addGestureRecognizer(singleTap)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
registerForKeyboardNotifications()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
deregisterFromKeyboardNotifications()
}
// MARK: Gesture recognizer
func singleTapGestureCaptured(sender: AnyObject) {
view.endEditing(true)
}
// MARK: Keyboard management
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWasShown), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillBeHidden), name: UIKeyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification) {
scrollView.scrollEnabled = true
let info : NSDictionary = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeFieldPresent = activeTextField {
if (!CGRectContainsPoint(aRect, activeFieldPresent.frame.origin)) {
scrollView.scrollRectToVisible(activeFieldPresent.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let info : NSDictionary = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
view.endEditing(true)
scrollView.scrollEnabled = false
}
// MARK: Text field management
func textFieldDidBeginEditing(textField: UITextField) {
activeTextField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
activeTextField = nil
}
}
And here is the inheriting class code:
class ViewController: ScrollViewController {
#IBOutlet weak var scrollViewOutlet: UIScrollView! {
didSet {
self.scrollView = self.scrollViewOutlet
}
}
// Your view controller functions
}
I hope this will help!

Loading a view makes the program crash

I'm still learning how the view works, but I found a problem that I couldn't solve...
I got the class GraficsBalancGlobalViewControllerwhich is subclass of the class GraficViewController
class GraficsBalancGlobalViewController: GraficViewController {
#IBAction func afegeixGrafic(sender: NSButton) {
addNewGrafic() // which is set on the GraficViewController
}
}
And when I perform the IBAction afegeixGrafic my program crashes on the line marked below:
class GraficViewController: NSViewController, GraficViewDataSource {
#IBAction func button(sender: NSButton) {
addNewGrafic()
}
func addNewGrafic() {
let frame = NSRect(x: 0, y: 0, width: self.view.bounds.width , height: self.view.bounds.width * 0.25)
let nouGrafic = GraficView(frame: frame)
scrollView.addSubview(nouGrafic) <---- BREAK here!
}
#IBOutlet weak var scrollView: NSView!
//...more code
}
The compiler says that:
fatal error: unexpectedly found nil while unwrapping an Optional value
but the button (IBAction) inside the GraficViewController works well!! So i suppose that the problem is related with the scrollView, but I have no idea of what can be.. It is initialized..
just to mention that the GraficView(frame: frame)is not the problem because I try it and works well.
I do believe ! is the culprit of your woes:
#IBOutlet weak var scrollView: NSView!
Xcode does generate this entry with force unwrap (!) for IBOutlets, but it should really be an optional (?) because you have no guaranties when that reference is going to be set. If you have some logic that depend on scrollView's existence, you can do so by relying on didSet:
#IBOutlet weak var scrollView: NSView? {
didSet {
guard let sview = scrollView else {
return // because scrollView is nil for some reason
}
// do your scrollView existence dependent logic here (eg. reload content)
}
}
func addNewGrafic() {
let frame = NSRect(x: 0, y: 0, width: self.view.bounds.width , height: self.view.bounds.width * 0.25)
let nouGrafic = GraficView(frame: frame)
scrollView?.addSubview(nouGrafic)
}
I hope this helps.

observing contentSize (CGSize) with KVO in swift

I'm trying to observering collectionView.contentSize like this :
func startObserveCollectionView() {
collectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old.union(NSKeyValueObservingOptions.New), context: &SearchDasboardLabelContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &SearchDasboardLabelContext {
if object === collectionView && keyPath! == "contentSize" {
print(change)
}
}
}
and in xcode terminal I got a NSSize not CGSize like this :
Optional(["old": NSSize: {320, 0}, "new": NSSize: {375, 39.5}, "kind": 1])
In objective-c I used method CGSizeValue
CGSize newContentSize = [[change objectForKey:NSKeyValueChangeNewKey] CGSizeValue];
Is there any method like CGSizeValue in swift
I have tried in swift var newContentSize = change[NSKeyValueChangeNewKey]?.CGSizeValue() but got error
could not find member 'CGSizeValue'
need help anyone? Thanks
With Swift 4, you can cast the result of the change dictionary for the key NSKeyValueChangeKey.newKey as being of type CGSize:
if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize {
/* ... */
}
The following UIViewController implementation shows how to set a KVO stack in order to observe the changes of the contentSize property of any UIScrollView subclass (e.g UITextView):
import UIKit
private var myContext = 0
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
/* ... */
override func viewDidLoad() {
super.viewDidLoad()
textView.addObserver(self, forKeyPath: #keyPath(UITextView.contentSize), options: [NSKeyValueObservingOptions.new], context: &myContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &myContext,
keyPath == #keyPath(UITextView.contentSize),
let contentSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
print("contentSize:", contentSize)
}
}
deinit {
textView.removeObserver(self, forKeyPath: #keyPath(UITextView.contentSize))
}
}
Note that with Swift 4, as an alternative to addObserver(_:, forKeyPath:, options:, context:) and observeValue(forKeyPath:, of:, change:, context:), you can use observe(_:, options:, changeHandler:) in order to track your UIScrollView subclass contentSize property changes:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
var observer: NSKeyValueObservation?
/* ... */
override func viewDidLoad() {
super.viewDidLoad()
let handler = { (textView: UITextView, change: NSKeyValueObservedChange<CGSize>) in
if let contentSize = change.newValue {
print("contentSize:", contentSize)
}
}
observer = textView.observe(\UITextView.contentSize, options: [NSKeyValueObservingOptions.new], changeHandler: handler)
}
}
Are you on iOS? Because I am, I did the same thing and arrived at the same question; why NSSize? Maybe that's just the xcode terminal playing a trick on us.
Anyway, you can cast it to an NSValue then you will be able to use CGSizeValue:
if let zeChange = change as? [NSString: NSValue] {
let oldSize = zeChange[NSKeyValueChangeOldKey]?.CGSizeValue()
let newSize = zeChange[NSKeyValueChangeNewKey]?.CGSizeValue()
}
There's a simpler and arguably swiftier alternative.
You can subclass UICollectionViewLayout (or any of its subclasses, like UICollectionViewFlowLayout) and override a computed property collectionViewContentSize. By calling super you'll get the contentSize of your collection and be able to delegate this value back to your code.
So you'll have something like this:
protocol FlowLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView?, didChange contentSize: CGSize)
}
class FlowLayout: UICollectionViewFlowLayout {
weak var delegate: FlowLayoutDelegate?
override var collectionViewContentSize: CGSize {
let contentSize = super.collectionViewContentSize
delegate?.collectionView(collectionView, didChange: contentSize)
return contentSize
}
}
Check out this example code:
if context == ApprovalObservingContext{
if let theChange = change as? [NSString: Bool]{
var newContentSize = change[NSKeyValueChangeNewKey]?.CGSizeValue()
}
}
This is not giving any error.

Resources