Select all text in a NSTextField using Swift - swift

How can I programatically select all the text in a NSTextField using Swift?
For UITextField theres a method like
textField.selectedTextRange = textField.textRangeFromPosition(...)

Try this in a playground:
import XCPlayground
import AppKit
let view = NSView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
XCPShowView("view", view)
let txtf = NSTextField(frame: CGRect(x: 50, y: 50, width: 200, height: 50))
view.addSubview(txtf)
txtf.stringValue = "falling asleep at the keyboard..."
txtf.selectText(nil) // Ends editing and selects the entire contents of the text field
var txt = txtf.currentEditor() // returns an NSText
txt.selectedRange // --> (0,33)
Command-click on selectedRange, then on NSText (its return type), which will jump you to its Swiftened header where you can check out its rich functionality...

Better solution:
import Cocoa
class TextFieldSubclass: NSTextField {
override func mouseDown(theEvent: NSEvent) {
super.mouseDown(theEvent)
if let textEditor = currentEditor() {
textEditor.selectAll(self)
}
}
}
Or for precise selection:
import Cocoa
class TextFieldSubclass: NSTextField {
override func mouseDown(theEvent: NSEvent) {
super.mouseDown(theEvent)
if let textEditor = currentEditor() {
textEditor.selectedRange = NSMakeRange(location, length)
}
}
}
Works for me:
import Cocoa
class TextFieldSubclass: NSTextField {
override func becomeFirstResponder() -> Bool {
let source = CGEventSourceCreate(CGEventSourceStateID.HIDSystemState)
let tapLocation = CGEventTapLocation.CGHIDEventTap
let cmdA = CGEventCreateKeyboardEvent(source, 0x00, true)
CGEventSetFlags(cmdA, CGEventFlags.MaskCommand)
CGEventPost(tapLocation, cmdA)
return true
}
}

Related

drawRect function not triggered when needDisplay is called

I am trying to create a custom class to build various graph views for the app eventually. The main class is the viewController class where I would like to present the graphs managed by the clGraph class. clGraph class will be used to process the data and create a graph view by vGraph class.
plotGraph function in the vGraphs is where I am struggling. It appears that the drawRect function is not called, however, setNeedsDisplay is called. Below is the test code that I have to understand and learn the basics. Requesting help to be able to draw a basic line on the targetView. Thank you.
class main : NSViewController {
#IBOutlet weak var targetView: NSView!
func plotGraph(){
let g = clGraphs()
g.initialize(graphCanvas: targetView, controller: self)
g.processData()
g.plotline()
}
}
class clGraphs {
var controller = NSViewController()
var graphCanvas = NSView()
var lineData = [Float]()
public func initialize (graphCanvas : NSView, controller : NSViewController) -> clGraphs{
self.graphCanvas = graphCanvas
self.controller = controller
return self
}
public func plotline(){
let lb = NSTextField(frame: NSRect(x: 10, y: 10, width: 100, height: 20))
lb.stringValue = "Test" //works fine on the view
graphCanvas.addSubview(lb)
print(graphCanvas.frame) //works fine
let g = vGraphs()
g.graphCanvas = graphCanvas
g.needsDisplay = true
}
public func processData(){
//Some logic to update lineData
}
}
class vGraphs: NSView{
var graphCanvas = NSView()
var lineData = [Float]()
override func draw(_ dirtyRect: NSRect) {
print("In the draw rect function")
}
override func setNeedsDisplay(_ invalidRect: NSRect) {
print("In the set needs display function")
plotGraph()
}
func plotGraph(){
graphCanvas.layer?.backgroundColor = NSColor.blue.cgColor //works fine
let aPath = NSBezierPath()
aPath.move(to: NSPoint(x: 50, y: 50))
aPath.line(to: NSPoint(x: 200, y: 200))
aPath.lineWidth = 10
aPath.close()
aPath.stroke()
// ERROR: CGContextRestoreGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
}
}

How to add UIGestureRecongnizer programmatically on custom views that are created via code?

I want to add a UITapGestureRecognizer to my view named SetView. My setviews are created programmatically on another custom view called GridView.
This is what I have tried so far but I am not seeing any action while tapping my subvies.
import UIKit
#IBDesignable
class GridView: UIView {
private(set) lazy var deckOfCards = createDeck()
lazy var grid = Grid(layout: Grid.Layout.fixedCellSize(CGSize(width: 128.0, height: 110.0)), frame: CGRect(origin: CGPoint(x: bounds.minX, y: bounds.minY), size: CGSize(width: bounds.width, height: bounds.height)))
lazy var listOfSetCard = createSetCards()
private func createDeck() -> [SetCard] {
var deck = [SetCard]()
for shape in SetCard.Shape.allShape {
for color in SetCard.Color.allColor {
for content in SetCard.Content.allContent {
for number in SetCard.Number.allNumbers {
deck.append(SetCard(shape: shape, color: color, content: content, rank: number))
}
}
}
}
deck.shuffle()
return deck
}
private func createSetCards() -> [SetView] {
var cards = [SetView]()
for _ in 0..<cardsOnScreen {
let card = SetView()
let contentsToBeDrawn = deckOfCards.removeFirst()
card.combinationOnCard.shape = contentsToBeDrawn.shape
card.combinationOnCard.color = contentsToBeDrawn.color
card.combinationOnCard.content = contentsToBeDrawn.content
card.combinationOnCard.rank = contentsToBeDrawn.rank
/* print(contentsToBeDrawn.color) */
addSubview(card)
cards.append(card)
}
return cards
}
override func layoutSubviews() {
super.layoutSubviews()
for index in listOfSetCard.indices {
let card = listOfSetCard[index]
if let rect = grid[index] {
card.frame = rect.insetBy(dx: 2.5, dy: 2.5)
card.frame.origin = rect.origin
print(card.frame.origin)
}
}
}
Here is the function didTap(sender: UITapGestureRecognizer) that I wrote on SetView:
#objc func didTap(sender: UITapGestureRecognizer) {
switch sender.state {
case .changed,.ended:
let rect = UIBezierPath(rect: bounds)
fillBoundingRect(inRect: rect, color: UIColor.gray)
default:
break
}
And ViewController:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...12 {
let card = game.drawModelCard()
game.deck.append(card)
}
}
lazy var game = SetGame()
weak var setView : SetView! {
didSet {
let tapGestureRecognizer = UITapGestureRecognizer(target:
setView, action: #selector(SetView.didTap(sender:)))
setView.isUserInteractionEnabled = true
setView.addGestureRecognizer(tapGestureRecognizer)
}
}
}
My subviews(SetViews) should change the background color once tapped.

Drag a CGRect using UIPanGestureRecognizer

I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
override func draw(_ rect: CGRect) {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
addGestureRecognizer(gesture)
drawRectangle(rect)
}
func drawRectangle(_ rect: CGRect) {
let path = UIBezierPath(rect: rectangle)
UIColor.black.set()
path.fill()
}
#objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
setNeedsDisplay()
recognizer.setTranslation(CGPoint.zero, in: self)
}
This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?
This is how you can do it easily. just copy paste and run this.
//
// RootController.swift
// SampleApp
//
// Created by Chanaka Caldera on 24/6/19.
// Copyright © 2019 homeapps. All rights reserved.
//
import UIKit
class RootController: UIViewController {
private var draggableView: UIView!
private var pangesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
setdragview()
}
}
extension RootController {
fileprivate func setdragview() {
draggableView = UIView()
draggableView.translatesAutoresizingMaskIntoConstraints = false
draggableView.backgroundColor = .lightGray
view.addSubview(draggableView)
let draggableviewConstraints = [draggableView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 10),
draggableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
draggableView.widthAnchor.constraint(equalToConstant: 100),
draggableView.heightAnchor.constraint(equalToConstant: 40)]
NSLayoutConstraint.activate(draggableviewConstraints)
pangesture = UIPanGestureRecognizer()
draggableView.isUserInteractionEnabled = true
draggableView.addGestureRecognizer(pangesture)
pangesture.addTarget(self, action: #selector(draggableFunction(_:)))
}
}
extension RootController {
#objc fileprivate func draggableFunction(_ sender: UIPanGestureRecognizer) {
view.bringSubviewToFront(draggableView)
let translation = sender.translation(in: self.view)
draggableView.center = CGPoint(x: draggableView.center.x + translation.x, y: draggableView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
print("drag works : \(translation.x)")
}
}
here is the demo,
Hope this will help. cheers !
Try like this (check comments through code):
#IBDesignable
class Rectangle: UIView {
#IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
#objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}
extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}
To draw rectangles on your view when panning you can do as follow:
import UIKit
class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
#objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}
Sample Project
I figured out most of the problem as to why the rectangle wasn't moving. It turns out that I misunderstood how variable getters and setters work in Swift. Nevertheless, I changed this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
to be a lazy variable:
lazy var rectangle: CGRect = {
return CGRect(x: 200,
y: 200,
width: self.frame.width / 6,
height: 15)
}()
The reason I needed get and set in the first place was because I use frame in my variable calculations, and that was not available until the view itself was fully instantiated. I also tweaked this code a bit:
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
and used minX instead of midX:
rectangle = CGRect(x: rectangle.minX + translation.x, y: rectangle.minY + translation.y, width: rectangle.width, height: rectangle.height)
This is because CGRect is initialized with the x and y parameters being the minX and minY.
This is good progress(at least the rectangle moves). However, I am not able to figure out why the rectangle only switches places after I have released my mouse, resulting in choppy movement.

I am trying to get an UIActivityIndicatorView to show when I am loading a UITableView

This problem has been answered several times before on this site, I have tried them all and none work. The difference I think is that I have a UITableView inside my UIViewController. I have tried when loading the data within viewDidLoad, here the screen I am coming from show until all is complete and my new view appears. I have also tried within viewDidAppear, here I have a blank table showing before the final view comes up.
I have tried 4 methods all from this site, I call pauseApp(n) before I start the load and restartApp(n) when completed
var spinner:UIActivityIndicatorView = UIActivityIndicatorView()
var loadingView = UIView()
var loadingLabel = UILabel()
var indicator = UIActivityIndicatorView()
#IBOutlet weak var tvTable: UITableView!
func pauseApp() {
tvTable.activityIndicatorView.startAnimating()
tvTable.activityIndicatorView.bringSubviewToFront(aIV)
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp1() {
spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
spinner.center = self.navBar.center
spinner.hidesWhenStopped = true
spinner.style = UIActivityIndicatorView.Style.gray
self.navigationController?.view.addSubview(spinner)
spinner.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp2() {
tvTable.activityIndicatorView.startAnimating()
indicator.startAnimating()
indicator.backgroundColor = UIColor.white
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp3() {
setLoadingScreen()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func restartApp() {
// sleep(2)
tvTable.activityIndicatorView.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp1() {
spinner.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp2() {
// sleep(2)
indicator.stopAnimating()
indicator.hidesWhenStopped = true
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp3() {
// sleep(2)
removeLoadingScreen()
UIApplication.shared.endIgnoringInteractionEvents()
}
private func setLoadingScreen() {
let width: CGFloat = 120
let height: CGFloat = 30
let x = (view.frame.width / 2) - (width / 2)
let y = (view.frame.height / 2) - (height / 2) - 20
loadingView.frame = CGRect(x: x, y: y, width: width, height: height)
// Sets loading text
loadingLabel.textColor = .gray
loadingLabel.textAlignment = .center
loadingLabel.text = "Loading..."
loadingLabel.frame = CGRect(x: 0, y: 0, width: 140, height: 30)
// Sets spinner
spinner.style = .gray
spinner.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
spinner.startAnimating()
// Adds text and spinner to the view
loadingView.addSubview(spinner)
loadingView.addSubview(loadingLabel)
view.addSubview(loadingView)
view.bringSubviewToFront(loadingView)
}
private func removeLoadingScreen() {
spinner.stopAnimating()
spinner.isHidden = true
loadingLabel.isHidden = true
}
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public var aIV: UIActivityIndicatorView = UIActivityIndicatorView()
public extension UITableView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let aIV = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return aIV
} else {
let aIV = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
aIV.style = .gray
aIV.color = .gray
aIV.backgroundColor = UIColor.black
aIV.center = center
aIV.hidesWhenStopped = true
addSubview(aIV)
setAssociatedObject(aIV, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
aIV.bringSubviewToFront(aIV)
return aIV
}
}
set {
addSubview(newValue)
setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
public extension NSObject {
func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
if let valueAsAnyObject = value {
objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
}
}
func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
return nil
}
return valueAsType
}
}
Verify your Interface Builder file, specifically the order in which the components are defined. Components higher up in the hierarchy may be hidden by those defined below them. Thus it's quite possible that your tableview hides your activity view.
You should be able to confirm this fairly quickly by hiding the table view and other other views that may be on top. Depending on your activity view settings, you may also need to do tvTable.activityIndicatorView.isHidden = false. Note that since UITableView implement a built-in scrollview, adding an activity view as a child to a UITableView may not be the the best course. You are better off defining it as a child of the tableView's superview; ref:
Your attempt with pauseApp1 could work with minor modifications, but only if your view controller is hosted inside a navigation controller. You should also always define any relationship only AFTER the view is added as a subview not before.
Starting a brand new project from scratch, here's how you can display an activity indicator by code:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// We add some delay for fun, absolutely not required!
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.showSpinner()
}
}
private func showSpinner() {
let spinner = UIActivityIndicatorView.init(style: .gray)
self.view.addSubview(spinner)
spinner.center = self.view.center
spinner.startAnimating()
spinner.isHidden = false
spinner.hidesWhenStopped = true
}
}
Thanks Again,
The solution is to add an activityIndeicatorView with Storyboard below our TableView
Then in viewDidAppear have
#IBOutlet weak var mySpinner: UIActivityIndicatorView!
override func viewDidAppear(_ animated: Bool) {
self.pauseApp()
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.doScreen()
}
}
func pauseApp() {
showSpinner()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func restartApp() {
mySpinner.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
private func showSpinner() {
mySpinner.startAnimating()
mySpinner.isHidden = false
mySpinner.hidesWhenStopped = true
}
The pauseApp call put the spinner on the screen, the doScreen does all the work and calls restartApp when it has finished. My doScreen does quite a lot of work going to a service to get the data, it takes about 2 seconds with a good internet connection, but much longer when the connection is poor.

How to make NSView draggable in Swift (OSX Mac app)?

I'm trying to make two NSView's draggable and I was wondering how to code that in Swift for OSX Mac app? I'm thinking to put two NSView's within an NSView and make them draggable within that NSView. For example, create NSViewA and nested within in are NSViewB and NSViewC (both draggable within NSViewA). I would like to code in Swift. Cheers!
You can do this fairly easily if you take advantage of the information passed on the responder chain. Consider this custom NSView class.
class DraggableView : NSView {
var startPoint: NSPoint?
var frameOrigin: NSPoint?
override func mouseDown(with event: NSEvent) {
startPoint = event.locationInWindow
frameOrigin = frame.origin
}
override func mouseDragged(with event: NSEvent) {
let offset = event.locationInWindow - startPoint!
frame.origin = frameOrigin! + offset
}
override func mouseUp(with event: NSEvent) {
startPoint = nil
frameOrigin = nil
}
}
To make the math work for NSPoint, I overloaded operators in the extension:
extension NSPoint {
static func -(pointA: NSPoint, pointB: NSPoint) -> NSPoint {
return NSPoint(x: pointA.x - pointB.x, y: pointA.y - pointB.y)
}
static func +(pointA: NSPoint, pointB: NSPoint) -> NSPoint {
return NSPoint(x: pointA.x + pointB.x, y: pointA.y + pointB.y)
}
}
Then your ViewController class is just setup code:
class ViewController: NSViewController {
let viewA: DraggableView = DraggableView()
let viewB: DraggableView = DraggableView()
override func viewDidLoad() {
super.viewDidLoad()
viewA.frame = NSRect(origin: .zero, size: NSSize(width: 100, height: 100))
viewB.frame = NSRect(origin: NSPoint(x: 125, y: 0), size: NSSize(width: 100, height: 100))
viewA.wantsLayer = true
viewB.wantsLayer = true
viewA.layer?.backgroundColor = NSColor.blue.cgColor
viewB.layer?.backgroundColor = NSColor.green.cgColor
view.addSubview(viewA)
view.addSubview(viewB)
}
}