ViewController looses animated position after navigation - swift

I want a view to appearing and disappear from the left (depending if it has some useful information for the user).
I want to view to be positioned using constraints, so I need it to be created from the storyboard.
In this code snippet, the view should be moved out of the way when to code appears.
But: When I segue to the next VC I can see that the view appears again at its original position and when going back from the VC to the initial VC it has, in fact, resumed the original position.
I played a little around with saving the "state" of the view in a variable, making it appear/disappear in the various lifecycles of the VC, but nothing really helped.
How is the best way to achieve this?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
var boxIsVisible = false
override func viewDidLoad() {
super.viewDidLoad()
}
var originalX:CGFloat = 0.0
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
originalX = self.myView.frame.origin.x
if boxIsVisible == false {
self.myView.center.x -= 200
}
}
#IBAction func slideInAction(sender: AnyObject) {
UIView.animateWithDuration(3.0, animations: {
self.myView.center.x = self.originalX
self.boxIsVisible = true
})
}
#IBAction func slideOutAction(sender: AnyObject) {
UIView.animateWithDuration(3.0, animations: {
self.myView.center.x = -200
self.boxIsVisible = false
})
}
}

Update leading constraint instead of the view's position, because you use auto layout.
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
var boxIsVisible = false
#IBOutlet weak var leadingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if boxIsVisible == false {
leadingConstraint.constant = -myView.frame.width
}
}
#IBAction func slideInAction(sender: AnyObject) {
UIView.animateWithDuration(3.0, animations: {
self.leadingConstraint.constant = -50
self.view.layoutIfNeeded()
self.boxIsVisible = true
})
}
#IBAction func slideOutAction(sender: AnyObject) {
UIView.animateWithDuration(3.0, animations: {
self.leadingConstraint.constant = -self.myView.frame.width
self.view.layoutIfNeeded()
self.boxIsVisible = false
})
}
}

Related

How to set NSSlider value in a NSToolbar - Swift Cocoa + Storyboard

I am quite new to Swift programming and I am trying to set a slider min, max and value in a NSToolbar. As an hypothetical exemple, I have a list of client and I want to use the slider in the toolbar to select a client data page. I will firt to load the client database in the NSViewController and count the number of client. Than I would like to set the slider in the toolbar minvalue to 1 and maxvalue to the number of client. I understand how to send slider values from the Windowcontroller to the ViewController but I did not found how to do the inverse , how to send data from the Viewcontroller to the Window controller in order to set the slider values.
I have attach an simple code based on this exemple https://github.com/gbdavid2/DavidCodes_macOS/tree/master/NSToolbar%20with%20Storyboards/NSToolbar%20with%20Storyboards
In this exemple, the Toolbar shows a Previous and an Next button that , when clicked, they change a counter value (count). I would like to send back that value from the ViewCoOntroller to the WindowController in order to display it in label and eventually, the slider value in the toolbar. Thanks for your help.
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//viewController.myLabel.stringValue = "boo"
}
#IBAction func previous(_ sender: Any) {
viewController.updateMyLabelText(newText: "Prev Button clicked! ")
}
#IBAction func next(_ sender: Any) {
viewController.updateMyLabelText(newText: "Next Button clicked! ")
}
}
import Cocoa
class ViewController: NSViewController {
var count : Int = 0
#IBOutlet weak var myLabel: NSTextField!
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 updateMyLabelText(newText: String){
if newText.contains("Prev") {count -= 1}
else if newText.contains("Next") {count += 1}
myLabel.stringValue = newText + String(count)
}
}
Another way to to achieve this is with Cocoa Bindings. Example:
In the toolbar are a Previous button, a Next button and a slider. The actions of the buttons are connected to the First Responder. The action methods are implemented in ViewController. The count property of ViewController has attributes #objc dynamic so it can be used with Cocoa Bindings.
class ViewController: NSViewController {
#objc dynamic var count: Int = 0
#IBAction func previous(_ sender: Any) {
count -= 1
}
#IBAction func next(_ sender: Any) {
count += 1
}
}
The slider in the toolbar is bound to the Window Controller, key path window.contentViewController.count.
In the view is a label with a number formatter. The value of the label is bound to the View Controller, key path count.
The window controller isn't subclassed.
There are multiple ways to achieve this.
One of the way is by creating a class [e.g: SliderManager] which keep tracks of current value and handles increment/decrement. You can get the current value of Slider with the help of Singleton in any Controller.
Here is an example implementation:
protocol SliderCountDelegate: NSObject {
func counterDidUpdate()
}
final class SliderCountManager {
static let shared = SliderCountManager()
var value: UInt8 = 0 // <-- Unsigned Integers: Only Positive numbers
weak var delegate: SliderCountDelegate?
public func increaseCounter() {
value += 1
delegate?.counterDidUpdate()
}
public func decreaseCounter() {
value -= 1
delegate?.counterDidUpdate()
}
}
Here is how you should use this in your code:
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
override func windowDidLoad() {
super.windowDidLoad()
}
#IBAction func previous(_ sender: Any) {
SliderCountManager.shared.increaseCounter()
print(SliderCountManager.shared.value) // <- Accessing Current value here
}
#IBAction func next(_ sender: Any) {
SliderCountManager.shared.decreaseCounter()
print(SliderCountManager.shared.value)
}
}
import Cocoa
class ViewController: NSViewController, SliderCountDelegate {
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
SliderCountManager.shared.delegate = self // Set Delegate to `self`
}
override var representedObject: Any? {
didSet {
}
}
// Protocol conformance
func counterDidUpdate() {
myLabel.stringValue = String(SliderCountManager.shared.value)
}
}
Thanks for the proposed solutions. It certainly put me in the wrigth direction.
Here is what I did. In the WindowController , I set a toolbar with 1) button «previous», 2) button «next» and 3) a slider «slider».
Those are linked to the proper IBOutler and IBaction in the WindowController.
The viewController have a textLabel «myLabel»
The 2 buttons and the slider change the slider_ptr value in the ViewControler and is sent to myLabel. Also, the slider.label change according to the slider_pointer and the slider_max values. Here is the code for the windowController:
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var slider: NSSlider!
#IBOutlet weak var sliderTB: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
setSlider() // set initial value based on ViewController
}
#IBAction func previous(_ sender: Any) {
viewController.previous (WindowController())
setSlider()
}
#IBAction func next(_ sender: Any) {
//viewController.updateMyLabelText(newText: "Prev Button clicked! ")
viewController.next (WindowController()) //send to VC function previous
// let pt = viewController.slider_ptr + 1
//let sMax = viewController.slider_max
setSlider()
//sliderTB.label = String(pt) + " de " + String(sMax)
}
#IBAction func sliderDidChange(_ sender: Any) {
viewController.sliderDidSlide (WindowController(), pointer: Int(slider.doubleValue))
setSlider()
// viewController.sliderDidSlide(PosiWC(), sValue: myslider.doubleValue)
}
func setSlider() {
/* myslider.minValue = 1
myslider.maxValue = Double(max)
myslider.integerValue = pointer*/
//print ("WCP58:" , myslider.integerValue )
let pt = viewController.slider_ptr
let sMax = viewController.slider_max
//slider (max : pt, pointer: sMax)
sliderTB.label = String(pt) + " de " + String(sMax)
slider.minValue = 1
slider.maxValue = Double(sMax)
slider.integerValue = pt
}
}
and for the Viewcontroller :
class ViewController: NSViewController {
var slider_ptr = 1 // slider position
var slider_max: Int = 0 //
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
slider_max = 250
myLabel.stringValue = String(slider_ptr)
}
override var representedObject: Any? {
didSet {
}
}
func previous(_ sender: Any) {
if slider_ptr > 1 {
slider_ptr -= 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func next(_ sender: Any) {
if slider_ptr < slider_max {
slider_ptr += 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func sliderDidSlide(_ sender: Any, pointer : Int) {
print (pointer)
slider_ptr = pointer
myLabel.stringValue = String(slider_ptr)
}
}

How to hide NavigationBar from sub view?

I am facing an issue of NavigationBar, I dont want it in subview (child view), I also used setNavigationBarHidden() method to hide but it is not working.
class VehicleSavingPopupViewController: UIViewController {
#IBOutlet weak var bottomView: UIView!
#IBOutlet weak var backGroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
animateView()
backGroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backGroundViewTapped(_:))))
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
private func animateView(){
UIView.animate(withDuration: 0.5, delay: 0, options: [.transitionCurlDown],
animations: { [weak self] in
guard let self = `self` else {return}
self.bottomView.center.y -= self.bottomView.bounds.height
}, completion: nil)
}
I set this in my own navigationController class but it should work in your viewDidLoad() method as well.
navigationController?.navigationBar.isHidden = true

Custom keyboard is crashing the app - Swift

I'm doing a test of a custom keyboard. This is what I need:
It has to have two UITextFields. Cannot be labels.
The keyboard is an embedded UIView.
The default keyboard should be disabled.
It cannot be a keyboard extension.
Not sure why the app is crashing. PS: Not all the keys are on the code yet. Here is an image of what I'm trying to do and the two View Controllers.
Edit: The error is: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
First ViewController:
import UIKit
class HomeVC: UIViewController, ButtonTapDelegate {
#IBOutlet var textField1: UITextField!
#IBOutlet var textField2: UITextField!
#IBOutlet var keyboardView: UIView!
var buttonPressed = [String]()
override func viewDidLoad() {
addKeyboard(view: keyboardView)
buttonPressed = [String]()
textField1.inputView = UIView()
textField2.inputView = UIView()
}
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
view.addSubview(keyboard.view)
addChild(keyboard)
}
func didTapButton(sender: UIButton) {
if sender.tag == 5 {
textField1.text?.append(contentsOf: " ")
} else if sender.tag == 6 {
textField1.text?.removeAll()
buttonPressed = [String]()
} else {
let val = sender.titleLabel?.text
textField1.text?.append(contentsOf: val!)
}
self.textField1.text = buttonPressed.joined(separator: "")
}
}
Here is the second View Controller:
import UIKit
protocol ButtonTapDelegate {
func didTapButton(sender: UIButton)
}
class KeyboardVC: UIViewController {
var delegate: ButtonTapDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttons(_ sender: UIButton) {
delegate.didTapButton(sender: sender)
print(sender)
}
}
var delegate: ButtonTapDelegate!
An implicitly unwrapped optional is essentially a promise that you're definitely going to give the variable a value before you try to access it. The problem in this case is that you haven't done that. Most likely, you want to do this in your first view controller:
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
keyboard.delegate = self // Now "delegate" will have a value before the function gets called
view.addSubview(keyboard.view)
addChild(keyboard)
}

delegation (while creating sideMenu, delegate? returns nil)

guys.
I'm asking for help. It seems a very easy task, but I can solve it for the whole day.
I'm trying to create a side menu using container view. When a user presses More button(barButtonItem), the whole view slide to the right and menu table appears. I know how to make it using Notifications. But I would like to solve it through delegation. Here is my storyboard.
enter image description here
and code:
import UIKit
class RootViewController: UIViewController, SideMenuDelegate {
#IBOutlet weak var leading: NSLayoutConstraint!
var sideMenuIsOpen = false
var sideMenu: MainViewController?
override func viewDidLoad() {
super.viewDidLoad()
sideMenu?.delegate = self
}
func openSideMenu() {
toggleSideMenu()
}
func toggleSideMenu() {
if sideMenuIsOpen {
leading.constant = 0
} else {
leading.constant = 240
}
}
}
and:
import UIKit
protocol SideMenuDelegate {
func openSideMenu()
}
class MainViewController: UIViewController {
var delegate: SideMenuDelegate?
#IBAction func toggleSideMenu(_ sender: UIBarButtonItem) {
if let delegateUnwrapped = delegate {
delegateUnwrapped.openSideMenu()
} else {
print("nil")
}
}
override func viewDidLoad() {
super.viewDidLoad()
BackgroundImageView.createBackground(insideView: self, image: .mainViewBackground)
}
}
Thank you!
This
var sideMenu: MainViewController? // is nil
override func viewDidLoad() {
super.viewDidLoad()
sideMenu?.delegate = self
}
has no reference to the current displayed main , it's nil when you present the main from the Root give it the reference

Swift: Updating a label from a second view controller

I have two view controllers. I also have a universal variable called number. The first view controller has a label on it called mainLabel. My second view controller has a button on it. When the button is pressed it should subtract 200 from the variable number then update the mainLabel label. I can not figure out how to make mainLabel a label that works on the second view controller too.
First View Controller
import UIKit
var number:Int = 0
class ViewController: UIViewController {
#IBOutlet weak var mainLabel: UILabel!
#IBAction func backgroundButton(sender: AnyObject) {
number = number + 1
mainLabel.text = "\(number)"
NSUserDefaults.standardUserDefaults().setObject(number, forKey: "number")
}
override func viewDidLoad() {
super.viewDidLoad()
if NSUserDefaults.standardUserDefaults().objectForKey("number") != nil {
number = NSUserDefaults.standardUserDefaults().objectForKey("number") as! Int
}
mainLabel.text = "\(number)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Second View Controller
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var buy1Label: UILabel!
#IBAction func buy1(sender: AnyObject) {
number = number - 200
buy1Label.text = "Bought!"
mainLabel.text = "\(number)"
}
}
class ViewController: UIViewController {
#IBOutlet weak var mainLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserverForName("NSNotificationName", object: nil, queue: nil) { (note) -> Void in
// Number changed, update your UILabel.
var number: Int = NSUserDefaults.standardUserDefaults().integerForKey("keyOfNumber")
self.mainLabel.text = "\(number)"
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func subtractNumberBy200() {
var number: Int = NSUserDefaults.standardUserDefaults().integerForKey("keyOfNumber")
number -= 200
NSUserDefaults.standardUserDefaults().setInteger(number, forKey: "keyOfNumber")
}
}
Hope this will works for you.
Wow, this question cuts to the core of what I have to deal with every day. Here is my first take on how to do this with notifications.
First, setup some common global things.
let NumberDidChangeNote = "NumberDidChangeNote" // A name for the notification.
// A function to get the value of number the same way every time.
func number() -> Int {
return NSUserDefaults.standardUserDefaults().integerForKey("number");
}
// A function to set the value of number the same way every time.
func setNumber(number: Int) {
NSUserDefaults.standardUserDefaults().setInteger(number, forKey: "number")
NSUserDefaults.standardUserDefaults().synchronize()
// Post a number did change notification
NSNotificationCenter.defaultCenter().postNotificationName(NumberDidChangeNote, object: nil)
}
The first view controller splits the roles of setting number and setting the label.
class ViewController: UIViewController {
#IBOutlet weak var mainLabel: UILabel!
#IBAction func backgroundButton(sender: AnyObject) {
setNumber(number() + 1)
}
dynamic func numberDidChange(note: NSNotification) {
mainLabel.text = "\(number())"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Add an observer which will call numberDidChange() anytime the number changes.
let center = NSNotificationCenter.defaultCenter()
center.addObserver(self, selector: "numberDidChange", name: NumberDidChangeNote, object: nil)
// Fake an initial notification to numberDidChange() to set the initial value of the label.
numberDidChange(NSNotification(name: NumberDidChangeNote, object: nil))
}
override func viewWillDisappear(animated: Bool) {
// Remove the observer when not on the screen.
let center = NSNotificationCenter.defaultCenter()
center.removeObserver(self, name: NumberDidChangeNote, object: nil)
super.viewWillDisappear(animated)
}
}
Now the second view controller doesn't have to worry about the first view controller at all.
class SecondViewController: UIViewController {
#IBOutlet weak var buy1Label: UILabel!
#IBAction func buy1(sender: AnyObject) {
setNumber(number() - 200)
buy1Label.text = "Bought!"
}
}