Swift change method in ViewDidLoad - ios

I'm trying to change a method in ViewDidLoad with this code:
In class declaration:
var nextValue: Int!
And in ViewDidLoad:
if nextValue == nil {
print("Hi")
} else if nextValue == 2 {
print("Hello")
}
And finally this function that changes the value of nextValue:
func buttonAction(sender: AnyObject) {
self.performSegueWithIdentifier("nextView", sender: self)
nextValue = 2
}
When I move back from "nextView" to the first view nextValue should be 2 but it is nil. What am I doing wrong?

Your understanding about view life cycle is wrong.
First, you declare your variable with nil value in class declaration.
then, during viewDidLoad method you check its value, finally you change its value with some button action.
However, when you leave your view controller screen via segue to nextView, your firstView gets deallocated and when you represent it again, the cycle goes back to declaration level. since you declare your variable value as nil, it will always show nil value.
if you want to retain its value, you need to save it somewhere else, NSUserDefault seems like a good choice to store its value.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
nextValue = NSUserDefaults.standardUserDefaults().valueForKey("nextValue") as? Int
if nextValue == nil {
print("Hi")
} else if nextValue == 2 {
print("Hello")
}
}
func buttonAction(sender: AnyObject) {
self.performSegueWithIdentifier("nextView", sender: self)
nextValue = 2
NSUserDefaults.standardUserDefaults().setInteger(2, forKey: "nextValue")
}

Related

How do you change an image on one view controller from another view controller in Swift 5

I'm currently working on a project for an end of the year assignment and I need help to change a UIImage in my first view controller when the user clicks an item in a list view on the second view controller. I'm using a normal show segue to get to the menu when a button is clicked and this to get back:
Code on second view that goes back to the first view
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss (animated: true, completion: nil)
building = true
print("\(squareType)")
print("BUILDING = \(building)")
}
How do I get this to activate code on my first view to change images. I already have perimeters that run through both views to tell if it's been clicked and what to make based off of it, but I can't update my images
This is the code I want the other view to activate:
code on my first view that I want to trigger after the second view is dismissed
func farmCreator() {
for i in 0...24 {
if allCells[i].material == "Forest" && squareType == "Cabin" {
imageBottom[i].image = UIImage(named:"Farms/2")
}
if allCells[i].material == "Forest" && squareType == "Forest Mine" {
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
if allCells[i].material == "Rock" && squareType == "Mine" {
imageBottom[i].image = UIImage(named:"Farms/1")
}
if allCells[i].material == "Water" && squareType == "Fishmonger" {
imageBottom[i].image = UIImage(named:"Farms/3")
}
if allCells[i].material == "Water" && squareType == "Water Mine" {
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
if allCells[i].material == "Plains" && squareType == "Farm"{
imageBottom[i].image = UIImage(named:"Farms/4")
}
if allCells[i].material == "Plains" && squareType == "Plain Mine"{
imageBottom[i].image = UIImage(named:"Farms/1") //Change
}
}
}
Ignore how poorly optimized it is, I'm new
I've tried messing with all of the overrides. These things:
override func viewDidDisappear(_ animated: Bool) {
print("VIEW DID DIS V1")
}
override func viewWillDisappear(_ animated: Bool) {
print("VIEW WILL DIS V1")
}
override func viewDidAppear(_ animated: Bool) {
print("VIEW DID APP V1")
}
override func viewWillAppear(_ animated: Bool) {
print("VIEW WILL APP V1")
}
But they only work on the view that is disappearing with my dismiss and not the view that it moves to. Would I have to use an unwind? How would I implement that??
You need to use delegation here. I can't see all your code so I'll have to come up with different names for some things.
Step 1: Create a protocol on top of your ViewController 2 class
protocol ViewController2Protocol {
func didTapRow(squareType: String, material: String)
}
class ViewController2: UIViewController {
...
// Contents of ViewController 2
}
Step 2: Create a weak var delegate that's the type of Protocol declared above
protocol ViewController2Protocol {
func didTapRow(squareType: String, material: String)
}
class ViewController2: UIViewController {
weak var delegate: ViewController2Protocol? // 2. Add this
...
// Contents of ViewController 2
}
Step 3: Call your delegate method and pass the required parameters in didSelectRow before dismissing
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Assuming you have: var material: String, and var building: String
delegate?.didTapRow(material: material, building: building) // 3. Not sure what parameters you need to send to ViewController1. Just guessing.
dismiss (animated: true, completion: nil)
}
Step 4: Conform to, and implement the protocol in ViewController 1
class ViewController1: UIViewController, ViewController2Protocol { // Conform to protocol
func didTapRow(squareType: String, material: String) { // Implement it
// Call the method you want to get triggered by tapping here
farmCreator(squareType: squareType, building: building) // Change this method to accept the parameters as needed
}
}
Step 5. Make sure you set ViewController1 as the delegate wherever you're transitioning to ViewController2 in your ViewController1 class. Again not sure what your navigation code looks like so, add this there. For example
// Given ViewController 2
let vc2 = ViewController2()
vc2.delegate = self
I can give more detail in step 5 if I know how your transitioning:
Is your segue from a UI element in your storyboard?
Is your segue from ViewController1 to ViewController2 in your storyboard?

Swift: Failing to set a [String] through a segue using the sender

Good day. I'm creating my first own app and have ran into an issue. I have a AR scene with clickable stuff which when you touch them a segue triggers into a ViewController and sets that view controllers labels and textview depending on what was touch on screen.
Explanations: 1. CaseViewController is the target view controller. 2. the "artNews" and "politicalNews" are string arrays in which I've written 3 strings, they are defined and are never nil.
Question: I get a crash due to the segueInputText being nil. Why does it become nil and how do I correct it?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! CaseViewController
destinationVC.segueInputText = sender as? [String]
print("\(String(describing: sender))")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchLocation = touches.first?.location(in: sceneView),
let hitNode = sceneView?.hitTest(touchLocation, options: nil).first?.node,
let nodeName = hitNode.name
else { return }
if nodeName == imageNameArray[0] {
performSegue(withIdentifier: "CaseViewController", sender: artNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
if nodeName == imageNameArray[1] {
performSegue(withIdentifier: "CaseViewController", sender: politicalNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
the CaseViewController has UILabels and UITextViews connected and this:
var segueInputText : [String]? {
didSet {
setupText()
}
}
func setupText() {
// Why are these values nil?
testLabel.text = segueInputText![0]
ingressLabel.text = segueInputText![1]
breadLabel.text = segueInputText![2]
testLabel.reloadInputViews()
ingressLabel.reloadInputViews()
breadLabel.reloadInputViews() //breadLabel is a UITextView
}
Thank you for reading my question!
Kind regards.
Remove didSet block as when you set the array inside prepare , observe triggers and the lbl is still nil
OR
func setupText() {
if testLabel == nil { // if one is nil then all of them too
return
}
}
Don't use the didSet observer in this case. It will never work.
In setupText() IBOutlets are accessed which are not connected yet at the moment prepare(for is called.
Remove the observer
var segueInputText : [String]?
and call setupText in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupText()
}
At the very moment when you do this:
destinationVC.segueInputText = sender as? [String]
the destination view controller has not yet loaded hence none of the outlets are connected so accessing any of them will crash your app as they are still nil.
You will have to assign any of the values you’d like to pass to the destination controller to a property and assign this property’s value to the the corresponding outlet in viewDidLoad. This way you make sure all outlets have connected.
For the same reason don’t use a property observer to assign the property’s value to any of the labels as this would happen, again, before the view controller had a chance to load…

Skip swift ViewController main functions such as viewDidDisappear

In my code, when a view disappears, a specific action occurs. I am doing it through the viewDidDisappear() function.
I have a specific button that when is pressed it goes to another view. I was wondering in what I way I could tell ONLY the function caused by a specific button to skip the viewDidDisappear().
I perfectly know I can add a sort of 'if' statement in the viewDidDisappear() but I was wondering if there was a more efficient method.
viewDidDisappear() is a UIViewController's lifecycle callback method that's called by the environment - as far as I know there is no way to disable its calling. And I don't think there should be - as I mentioned, it is a part of UIViewController's lifecycle, not calling it would break the contract - see its documentation.
Therefore you have to (and you should) achieve what you want by using if statement.
Do something like this:
fileprivate var skipDisappearingAnimation = false
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
prepareInterfaceForDisappearing()
}
fileprivate func prepareInterfaceForDisappearing() {
guard !skipDisappearingAnimation else {
// reset each time
skipDisappearingAnimation = false
return
}
// do the stuff you normally need
}
#objc fileprivate func buttonPressed(_ sender: UIButton) {
skipDisappearingAnimation = true
// navigate forward
}
It cannot be done; you must handle the case manually with if, something like:
var shouldSkip: Bool = false
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if !shouldSkip {
// your code goes here
}
shouldSkip = false // don't forget to set should skip to false again
}
#IBAction func buttonDidTap(_ sender: Any) {
shouldSkip = true // this will avoid run your code
// your code here
}

Swift 3 : Can not convert value of type 'CircleMenu' to expected argument type 'CircleMenu'

I have a custom button subclass of UIButton
/// A Button object with pop ups buttons
open class CircleMenu: UIButton {
/// The object that acts as the delegate of the circle menu.
#IBOutlet weak open var delegate: /*AnyObject?*/ CircleMenuDelegate?
......................
further down in that same class there is a function that detects when a button will be selected.
func buttonHandler(_ sender: CircleMenuButton) {
guard let platform = self.platform else { return }
self.delegate?.circleMenu?(self, buttonWillSelected: sender, atIndex: sender.tag)
let circle = CircleMenuLoader(radius: CGFloat(distance),
strokeWidth: bounds.size.height,
platform: platform,
color: sender.backgroundColor)
if let container = sender.container { // rotation animation
sender.rotationAnimation(container.angleZ + 360, duration: duration)
container.superview?.bringSubview(toFront: container)
}
if let buttons = buttons {
circle.fillAnimation(duration, startAngle: -90 + Float(360 / buttons.count) * Float(sender.tag)) { [weak self] _ in
self?.buttons?.forEach { $0.alpha = 0 }
}
circle.hideAnimation(0.5, delay: duration) { [weak self] _ in
if self?.platform?.superview != nil { self?.platform?.removeFromSuperview() }
}
hideCenterButton(duration: 0.3)
showCenterButton(duration: 0.525, delay: duration)
if customNormalIconView != nil && customSelectedIconView != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {
self.delegate?.circleMenu?(self, buttonDidSelected: sender, atIndex: sender.tag)
})
}
}
}
to go with the delegate variable above, I've got another class to manage the delegate.
#objc public protocol CircleMenuDelegate {
#objc optional func circleMenu(_ circleMenu: CircleMenu, buttonWillSelected sender: UIButton, atIndex: Int)
}
I am trying to access the data, specifically the index of the button that is being pressed in another class, and just while I'm playing around if set it up within a button
class AppMainViewController: UIViewController, CircleMenuDelegate {
#IBAction func getValue(_ sender: Any) {
CircleMenuDelegate.circleMenu(CircleMenu, buttonWillSelected: UIButton, atIndex: Int)
}
}
This is throwing me an error "Can not convert value of type 'CircleMenu' to expected argument type 'CircleMenu'"
------EDIT------
just adding the circleMenu call in a function within the AppMainViewController class stops the type error above but then i don't know how to get the values of buttonWillSelected and atIndex out.
func circleMenu(_ cm: CircleMenu, buttonWillSelected sender: UIButton, atIndex: Int) {
print(sender.tag)
}
Also some of the textbooks, online searches show something similar to:
CircleMenu().delegate = self
however there is no option to select delegate after CircleMenu().dropdown options
What I suspect that you are passing Class name CircleMenu inside the delegate method call instead of object of CircleMenu Class.
CircleMenuDelegate.circleMenu(**Circle Menu Object Here**, buttonWillSelected: UIButton, atIndex: Int)

Using "Next" as a Return Key

I use the "Next" value for the "Return Key" to get the Next button in place of the Done button, but (obviously) pressing it doesn't automatically move to the next UITextField in my view.
What's the right way to do this? I have seen many answers, but anyone have a swift solution?
Make sure your text fields have their delegate set and implement the textFieldShouldReturn method. This is the method that is called when the user taps the return key (no matter what it looks like).
The method might look something like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == self.field1 {
self.field2.becomeFirstResponder()
}
return true
}
The actual logic in here might vary. There are numerous approaches, and I'd definitely advise against a massive if/else chain if you have lots of text fields, but the gist here is to determine what view is currently active in order to determine what view should become active. Once you've determined which view should become active, call that view's becomeFirstResponder method.
For some code cleanliness, you might consider a UITextField extension that looks something like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And then change our textFieldShouldReturn method to look like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.nextField?.becomeFirstResponder()
return true
}
Once you've done this, it should simply be a matter of setting each text field's new nextField property in viewDidLoad:
self.field1.nextField = self.field2
self.field2.nextField = self.field3
self.field3.nextField = self.field4
self.field4.nextField = self.field1
Although if we really wanted, we could prefix the property with #IBOutlet, and that would allow us to hook up our "nextField" property right in interface builder.
Change the extension to look like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
#IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And now hook up the nextField property in interface builder:
(Set up your delegate while you're here too.)
And of course, if the nextField property returns nil, the keyboard just hides.
Here is an example in Swift:
I created a screen with 6 UITextFields. I assigned them the tags 1 through 6 in Interface Builder. I also changed the Return key to Next in IB. Then I implemented the following:
import UIKit
// Make your ViewController a UITextFieldDelegate
class ViewController: UIViewController, UITextFieldDelegate {
// Use a dictionary to define text field order 1 goes to 2, 2 goes to 3, etc.
let nextField = [1:2, 2:3, 3:4, 4:5, 5:6, 6:1]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Make ourselves the delegate of the text fields so that textFieldShouldReturn
// will be called when the user hits the Next/Return key
for i in 1...6 {
if let textField = self.view.viewWithTag(i) as? UITextField {
textField.delegate = self
}
}
}
// This is called when the user hits the Next/Return key
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Consult our dictionary to find the next field
if let nextTag = nextField[textField.tag] {
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
// Have the next field become the first responder
nextResponder.becomeFirstResponder()
}
}
// Return false here to avoid Next/Return key doing anything
return false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
There is nothing wrong with the other answers, this is just a different approach with the benefit of being more focused on OOP - imho (although this is a bit more work up front, it can be reused). In the storyboard, I start off adding tags with a distinct range (e.g 800-810) that define the specific order of the fields I want to move between. This has the benefit of working across all subviews in the main view so that one can navigate between UITextField's and UITextView's (and any other control) as needed.
Generally - I typically try to have view controllers message between views and custom event handler objects. So I use a message (aka, NSNotification) passed back to the view controller from a custom delegate class.
(TextField Delegate Handler)
Note: In AppDelegate.swift: let defaultCenter = NSNotificationCenter.defaultCenter()
//Globally scoped
struct MNGTextFieldEvents {
static let NextButtonTappedForTextField = "MNGTextFieldHandler.NextButtonTappedForTextField"
}
class MNGTextFieldHandler: NSObject, UITextFieldDelegate {
var fields:[UITextField]? = []
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
textField.backgroundColor = UIColor.yellowColor()
}
func textFieldDidEndEditing(textField: UITextField) {
textField.backgroundColor = UIColor.whiteColor()
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldClear(textField: UITextField) -> Bool {
return false
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
//passes the message and the textField (with tag) calling the method
defaultCenter.postNotification(NSNotification(name: MNGTextFieldEvents.NextButtonTappedForTextField, object: textField))
return false
}
}
This allows my view controller to remain focused on it's main job of handling the messaging between objects, model and view.
(View Controller receives a message from the delegate and passes instructions using the advanceToNextField function)
Note: In my storyboard my custom handler classes are defined using an NSObject and that object is linked into the storyboard as a delegate for the controls that I need monitored. Which causes the custom handler class to be initialized automatically.
class MyViewController: UIViewController {
#IBOutlet weak var tagsField: UITextField! { didSet {
(tagsField.delegate as? MNGTextFieldHandler)!.fields?.append(tagsField)
}
}
#IBOutlet weak var titleField: UITextField!{ didSet {
(titleField.delegate as? MNGTextFieldHandler)!.fields?.append(titleField)
}
}
#IBOutlet weak var textView: UITextView! { didSet {
(textView.delegate as? MNGTextViewHandler)!.fields?.append(textView)
}
}
private struct Constants {
static let SelectorAdvanceToNextField = Selector("advanceToNextField:")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
registerEventObservers()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
deRegisterEventObservers()
}
func advanceToNextField(notification:NSNotification) {
let currentTag = (notification.object as! UIView).tag
for aView in self.view.subviews {
if aView.tag == currentTag + 1 {
aView.becomeFirstResponder()
}
}
}
func registerEventObservers () {
defaultCenter.addObserver(self, selector: Constants.SelectorAdvanceToNextField, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
func deRegisterEventObservers() {
defaultCenter.removeObserver(self, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
....
}
Just another way to achieve the result that I found helpful. My app had 11 text fields followed by a text view. I needed to be able to cycle through all fields using the next key and then resign the keyboard following the textview (i.e. other notes).
In the storyboard, I set the tag on all of the fields (both text and textview) starting with 1 through 12, 12 being the textview.
I'm sure there are other ways to do it and this method isn't perfect, but hopefully it helps someone.
In code, I wrote the following:
func textFieldShouldReturn(textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
//Handle Textview transition, Textfield programmatically
if textField.tag == 11 {
//Current tag is 11, next field is a textview
self.OtherNotes.becomeFirstResponder()
} else if nextTag > 11 {
//12 is the end, close keyboard
textField.resignFirstResponder()
} else {
//Between 1 and 11 cycle through using next button
let nextResponder = self.view.viewWithTag(nextTag) as? UITextField
nextResponder?.becomeFirstResponder()
}
return false
}
func textFieldDidEndEditing(textField: UITextField) {
textField.resignFirstResponder()
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
//Remove keyboard when clicking Done on keyboard
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
Another approach, if you're using storyboards, you can change the textfield's attribute for Return Key.
Currently you have the following options: Default (Return), Go, Google, Join, Next, Route, Search, Send, Yahoo, Done, Emergency Call, Continue

Resources