how to get a textview to autoscroll down when theres a new line - swift

in swift 4.0 for cocoa applications
how do you get a textview to autoscroll down when theres a new line
of text added into it?
is there a built in function for this?
i can't seem to find a way to do this.
chatplace.scroll(<#T##point: NSPoint##NSPoint#>)

Here's a simple example that works:
import UIKit
class ViewController: UIViewController {
var count = 0
var timer:Timer?
#IBOutlet weak var textView: UITextView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textView.isScrollEnabled = true
textView.becomeFirstResponder()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer(timeInterval: 0.125, repeats: true, block: { (timer) in
self.count += 1
self.textView.text.append("\n\(self.count) This is another line")
})
if let timer = timer {
RunLoop.current.add(timer, forMode: .commonModes)
}
}
}
The result:
If you want to scroll programmatically this should do it:
func scrollToEnd(_ someTextView:UITextView) {
let bottom = NSMakeRange(someTextView.text.lengthOfBytes(using: .utf8)-1, 1)
someTextView.scrollRangeToVisible(bottom)
}

Related

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

How to transfer array variables between tab bar controllers?

I'm trying to understand the life cycle of view controllers. I need to use TabBar to switch controllers. And when switching controllers, I need the label to display life-cycle methods, not only of the controller on which I now find, but also from others.
I created an empty array private var arrayOfMethods = [String]() in which I add a triggered method every time.
class ViewController: UIViewController {
private var arrayOfMethods = [String]()
#IBOutlet var greenLabel: UILabel!
#IBOutlet var blueLabel: UILabel!
#IBOutlet var yellowLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
printMessage()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
printMessage()
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
printMessage()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
printMessage()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
printMessage()
}
func printMessage(line: Int = #line, function: String = #function) {
print("\(title ?? "nil"). Line: \(line) of function \(function)")
arrayOfMethods.append(function)
let string = arrayOfMethods.joined(separator: "\n")
greenLabel.text = "\(title ?? "nil") \(string)"
}
}
You can create a common class and call its function from all of your view controllers
class Helper: NSObject {
private var arrayOfMethods = [String]()
static let shared = Helper()
let mainLabel = UITextView(frame: CGRect(x: 100, y: 100, width: 300, height: 300))
private override init() {
super.init()
}
func printMessage(vc: UIViewController, line: Int = #line, function: String = #function) {
print("\(vc.title ?? "nil"). Line: \(line) of function \(function)")
arrayOfMethods.append( (vc.title ?? "nil") + "-" + function)
let string = arrayOfMethods.joined(separator: "\n")
guard let window = UIApplication.shared.keyWindow else { return }
if !window.subviews.contains(mainLabel) {
window.addSubview(mainLabel)
window.bringSubviewToFront(mainLabel)
}
mainLabel.text = string
}
}
And call this singleton class method from all your view controllers like this
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Helper.shared.printMessage(vc: self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Helper.shared.printMessage(vc: self)
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Helper.shared.printMessage(vc: self)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
Helper.shared.printMessage(vc: self)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
Helper.shared.printMessage(vc: self)
}
}
Output

swift how to update first controller from logic in second one vai protocol and delegate pattern?

I have a label in first view controller ViewController, and a func getting date avery second in second vc. I'd like to update label in first after timer starts in second. is it good to use protocol-delegate pattern? at this moment it is not working, time is going but not updating the view in first VC
my struct for protocol
protocol ViewControllerDelegate: class {
func changeLabelText(textToPass: String)
}
in first viewController
class ViewController: UIViewController, ViewControllerDelegate {
#IBOutlet weak var mainLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func changeLabelText(textToPass: String) {
self.mainLabel.text = textToPass
self.view.layoutIfNeeded()
}
#IBAction func buttonTapped(_ sender: UIButton) {
let nextVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
nextVC.delegateSubscriber = self
present(nextVC, animated: true, completion: nil)
}
}
in secondVC
class SecondViewController: UIViewController {
//MARK: speed timer feature 1/3
private weak var timer: Timer?
private var timerDispatchSourceTimer : DispatchSourceTimer?
weak var delegateSubscriber : ViewControllerDelegate?
#IBOutlet weak var myTxtField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
startTimer(every: 1)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("appeared")
stopTimer()
}
private func startTimer(every timeInterval: TimeInterval) {
if #available(iOS 10.0, *) {
timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in
let dateToPass = Date().description
print(dateToPass)
self?.delegateSubscriber?.changeLabelText(textToPass: dateToPass)
}
}
}
//MARK: speed timer feature 3/3
private func stopTimer() {
timer?.invalidate()
//timerDispatchSourceTimer?.suspend() // if you want to suspend timer
timerDispatchSourceTimer?.cancel()
}
#IBAction func buttonTapped(_ sender: UIButton) {
// delegateSubscriber?.changeLabelText(textToPass: self.myTxtField.text ?? "error")
dismiss(animated: true, completion: nil)
}
}
Just remove [weak self] from Timer closure
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
let dateToPass = Date().description
print(dateToPass)
self.delegateSubscriber?.changeLabelText(textToPass: dateToPass)
}
... then self isn't optional

Deselect the text in a NSTextField

Is there an easy way to deselect an NSTextField after pressing enter?
First you will need to make your view controller the delegate of your text field. Then you override NSControl instance method controlTextDidEndEditing(_:), get your textfield current editor selected range
and from the main thread set it back to your textfield:
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
override func controlTextDidEndEditing(_ obj: Notification) {
if let selectedRange = textField.currentEditor()?.selectedRange {
DispatchQueue.main.async {
self.textField.currentEditor()?.selectedRange = selectedRange
}
}
}
}
Here is one way I did it.
By disabling it with isSelectable and isEditable and then setting a timer to re-enable it after 0.5s
#IBAction func timeCodeChanged(_: NSTextField) {
timecodeLabel.isSelectable = false
timecodeLabel.isEditable = false
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(reEnableLabel), userInfo: nil, repeats: false)
}
#objc func reEnableLabel() {
timecodeLabel.isSelectable = true
timecodeLabel.isEditable = true
}

ViewController looses animated position after navigation

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