I'm experimenting with sticker iMessage apps in iOS 10 and I'm running into an issue with the override func didStartSending(_ message: MSMessage, conversation: MSConversation) method in MSMessagesAppViewController. When "peeling" a sticker from an MSStickerView, I would expect to receive some sort of callback on the didStartSending method. But it appears this is not the case. Does anyone know if this is the expected behavior and/or if there's another way to subscribe to callbacks for when these stickers are peeled, dragged, and dropped into the MSConversation? I realize that didStartSending is reserved for when the user taps the send button, but surely there should be some way of knowing when users drag MSStickers without hacking together some UIView dragging/rect-reading heuristic.
Messages View Controller:
class MessagesViewController: MSMessagesAppViewController {
var nYCStickersBroswerViewController: NYCStickersBroswerViewController!
override func viewDidLoad() {
super.viewDidLoad()
nYCStickersBroswerViewController = NYCStickersBroswerViewController(stickerSize: .regular)
nYCStickersBroswerViewController.view.frame = self.view.frame
self.addChildViewController(nYCStickersBroswerViewController)
nYCStickersBroswerViewController.didMove(toParentViewController: self)
self.view.addSubview(nYCStickersBroswerViewController.view)
nYCStickersBroswerViewController.loadStickers()
nYCStickersBroswerViewController.stickerBrowserView.reloadData()
}
...
override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
// Called when the user taps the send button.
print(message) // should this not contain the sticker that is peeled, dragged, and dropped into the conversation?
}
}
Sticker Browser:
import Foundation
import UIKit
import Messages
class ASSticker: MSSticker {
var identifier: String?
}
class NYCStickersBroswerViewController: MSStickerBrowserViewController {
var stickers = [ASSticker]()
override func viewDidLoad() {
super.viewDidLoad()
}
func changeBrowswerViewBackgroundColor(color: UIColor) {
stickerBrowserView.backgroundColor = color
}
func loadStickers() {
createSticker(name: "brooklyn", localizedDescription: "Brooklyn Bridge Sticker")
createSticker(name: "liberty", localizedDescription: "Statue of Liberty Sticker")
createSticker(name: "love", localizedDescription: "I Love New York Sticker")
createSticker(name: "mets", localizedDescription: "New York Mets Sticker")
createSticker(name: "rangers", localizedDescription: "New York Rangers Sticker")
createSticker(name: "subway", localizedDescription: "New York City MTA Subway Train Sticker")
}
func createSticker(name: String, localizedDescription: String) {
guard let stickerPath = Bundle.main.pathForResource(name, ofType: "png") else {
print("Call ae cab, you're intoxicated.")
return
}
let stickerURL = URL(fileURLWithPath: stickerPath)
let sticker: ASSticker
do {
try sticker = ASSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
sticker.identifier = "something unique"
stickers.append(sticker)
} catch {
print("Call a cab, you're intoxicated.")
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return self.stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return self.stickers[index]
}
}
Here's a subclass and delegate that will tie into the tap and long press gesture recognizers that MSStickerView is using for select and peel interactions. If the implementation of MSStickerView changes this may no longer provide events, but shouldn't crash.
import UIKit
import Messages
protocol InstrumentedStickerViewDelegate: class {
func stickerViewDidSelect(stickerView: MSStickerView)
func stickerViewDidPeel(stickerView: MSStickerView)
}
class InstrumentedStickerView: MSStickerView {
weak var delegate: InstrumentedStickerViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
for gestureRecognizer in gestureRecognizers ?? [] {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer {
tapGestureRecognizer.addTarget(self, action: #selector(didTap))
} else if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer {
longPressGestureRecognizer.addTarget(self, action: #selector(didLongPress))
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func didTap(tapGestureRecognizer: UITapGestureRecognizer) {
if tapGestureRecognizer.state == .Recognized {
delegate?.stickerViewDidSelect(self)
}
}
func didLongPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .Began {
delegate?.stickerViewDidPeel(self)
}
}
}
This is a workaround for sticker peeled and tapped events, it is not guaranteed that a particular sticker will be inserted but an approximation of such data points - you will have to use your subclass of MSStickerView. This solution is not futureproof, but works for the time being IMHO, therefore I welcome other ideas.
import UIKit
import Messages
class CustomStickerView : MSStickerView{
class GestureRecognizerReceiver : NSObject, UIGestureRecognizerDelegate{
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
let _recognizerDelegate = GestureRecognizerReceiver()
weak var _recognizer: UITapGestureRecognizer? = nil
func setupTapRecognizer(){
if _recognizer == nil {
let r = UITapGestureRecognizer(target: self, action: #selector(_customTapReceived))
r.cancelsTouchesInView = false
r.delegate = _recognizerDelegate
addGestureRecognizer(r)
_recognizer = r
}
}
func _customTapReceived(){
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Inserted", description: s.localizedDescription)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Peeled", description: s.localizedDescription)
}
}
}
Usage:
let sv = CustomStickerView(frame: _stickerViewHolder.bounds, sticker: sticker)
sv.setupTapRecognizer()
_stickerViewHolder.addSubview(sv)
sv.startAnimating()
Related
This is the barcode scanning tutorial I used in my program, so that you have a lot more context when you read my code: Link
Here is what my program does so far: Essentially, when I scan an item's barcode with my phone, the UIAlert pops up with the barcode ID displayed and a button prompting the user to open the "Results" page. This is all fine and good, but how do I pass that same scanned barcode ID into a label on the Result's page? I have been stuck on this for 2 days now, even though it seems like such an easy task.
Any help is much appreciated <3
Here is my relevant code:
ProductCatalog.plist ->
Link to Image
Scanner_ViewController.swift (first View Controller) ->
import UIKit
import AVFoundation
class Scanner_ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ScannerDelegate
{
private var scanner: Scanner?
override func viewDidLoad()
{
super.viewDidLoad()
self.scanner = Scanner(withDelegate: self)
guard let scanner = self.scanner else
{
return
}
scanner.requestCaptureSessionStartRunning()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Mark - AVFoundation delegate methods
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
guard let scanner = self.scanner else
{
return
}
scanner.metadataOutput(output,
didOutput: metadataObjects,
from: connection)
}
// Mark - Scanner delegate methods
func cameraView() -> UIView
{
return self.view
}
func delegateViewController() -> UIViewController
{
return self
}
func scanCompleted(withCode code: String)
{
print(code)
showAlert_Success(withTitle: (code))
}
private func showAlert_Success(withTitle title: String)
{
let alertController = UIAlertController(title: title, message: "Product has been successfully scanned", preferredStyle: .alert)
// programatically segue to the next view controller when the UIAlert pops up
alertController.addAction(UIAlertAction(title:"Get Results", style: .default, handler:{ action in self.performSegue(withIdentifier: "toAnalysisPage", sender: self) }))
present(alertController, animated: true)
}
}
Scanner.Swift (accompanies Scanner_ViewController.swift)->
import Foundation
import UIKit
import AVFoundation
protocol ScannerDelegate: class
{
func cameraView() -> UIView
func delegateViewController() -> UIViewController
func scanCompleted(withCode code: String)
}
class Scanner: NSObject
{
public weak var delegate: ScannerDelegate?
private var captureSession : AVCaptureSession?
init(withDelegate delegate: ScannerDelegate)
{
self.delegate = delegate
super.init()
self.scannerSetup()
}
private func scannerSetup()
{
guard let captureSession = self.createCaptureSession()
else
{
return
}
self.captureSession = captureSession
guard let delegate = self.delegate
else
{
return
}
let cameraView = delegate.cameraView()
let previewLayer = self.createPreviewLayer(withCaptureSession: captureSession,
view: cameraView)
cameraView.layer.addSublayer(previewLayer)
}
private func createCaptureSession() -> AVCaptureSession?
{
do
{
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(for: .video) else
{
return nil
}
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
let metaDataOutput = AVCaptureMetadataOutput()
// add device input
if captureSession.canAddInput(deviceInput) && captureSession.canAddOutput(metaDataOutput)
{
captureSession.addInput(deviceInput)
captureSession.addOutput(metaDataOutput)
guard let delegate = self.delegate,
let viewController = delegate.delegateViewController() as? AVCaptureMetadataOutputObjectsDelegate else
{
return nil
}
metaDataOutput.setMetadataObjectsDelegate(viewController,
queue: DispatchQueue.main)
metaDataOutput.metadataObjectTypes = self.metaObjectTypes()
return captureSession
}
}
catch
{
// handle error
}
return nil
}
private func createPreviewLayer(withCaptureSession captureSession: AVCaptureSession,
view: UIView) -> AVCaptureVideoPreviewLayer
{
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
return previewLayer
}
private func metaObjectTypes() -> [AVMetadataObject.ObjectType]
{
return [.qr,
.code128,
.code39,
.code39Mod43,
.code93,
.ean13,
.ean8,
.interleaved2of5,
.itf14,
.pdf417,
.upce
]
}
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
self.requestCaptureSessionStopRunning()
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let scannedValue = readableObject.stringValue,
let delegate = self.delegate
else
{
return
}
delegate.scanCompleted(withCode: scannedValue)
}
public func requestCaptureSessionStartRunning()
{
self.toggleCaptureSessionRunningState()
}
public func requestCaptureSessionStopRunning()
{
self.toggleCaptureSessionRunningState()
}
private func toggleCaptureSessionRunningState()
{
guard let captureSession = self.captureSession
else
{
return
}
if !captureSession.isRunning
{
captureSession.startRunning()
}
else
{
captureSession.stopRunning()
}
}
}
Analysis_ViewController.swift (second view controller) ->
Right now, the forKey: has been hard-coded to item ID 8710908501708 because I have no idea how to actually pass camera-scanned ID's into the second View Controller :/
import UIKit
class Analysis_ViewController: UIViewController
{
#IBOutlet weak var productTitle: UILabel!
func getData()
{
let path = Bundle.main.path(forResource:"ProductCatalog", ofType: "plist")
let dict:NSDictionary = NSDictionary(contentsOfFile: path!)!
if (dict.object(forKey: "8710908501708" as Any) != nil)
{
if let levelDict:[String : Any] = dict.object(forKey: "8710908501708" as Any) as? [String : Any]
{
// use a for loop to iterate through all the keys and values in side the "Levels" dictionary
for (key, value) in levelDict
{
// if we find a key named whatever we care about, we can print out the value
if (key == "name")
{
productTitle.text = (value as! String)
}
}
}
}
}
// listing the better options that are safer in comparison to the scanned product image
override func viewDidLoad()
{
super.viewDidLoad()
getData()
}
}
Do you have a variable to hold the scanned ID in your view controllers? If not, you can add var itemID: String? to both Scanner_ViewController and Analysis_ViewController.
Then in your func where you get the scanned code, you can set it to the variable.
func scanCompleted(withCode code: String) {
print(code)
itemID = code // Saves the scanned code to your var
showAlert_Success(withTitle: (code))
}
For passing data to another view controller via segue, you might want to look into this UIViewController method for segues: documentation here. This answer also might help.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAnalysisPage" {
if let viewController = segue.destination as? Analysis_ViewController {
viewController.itemID = itemID
}
}
}
I would like to capture keyevents in my little app.
What I have done:
class ViewController : NSViewController {
...
override func keyDown(theEvent: NSEvent) {
if theEvent.keyCode == 124 {
println("abc")
} else {
println("abcd")
}
}
override var acceptsFirstResponder: Bool {
return true
}
override func becomeFirstResponder() -> Bool {
return true
}
override func resignFirstResponder() -> Bool {
return true
}
...
}
What happens:
When a key pressed, the Funk sound effect plays.
I've seen many posts talking about how this is a delegate the belongs to NSView and NSViewController does not have access. But the keydown function override auto completes in a class of type NSViewController leading me to believe that this is wrong.
Xcode 8.2.1 • Swift 3.0.2
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) {
self.flagsChanged(with: $0)
return $0
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
self.keyDown(with: $0)
return $0
}
}
override func keyDown(with event: NSEvent) {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.command] where event.characters == "l",
[.command, .shift] where event.characters == "l":
print("command-l or command-shift-l")
default:
break
}
textField.stringValue = "key = " + (event.charactersIgnoringModifiers
?? "")
textField.stringValue += "\ncharacter = " + (event.characters ?? "")
}
override func flagsChanged(with event: NSEvent) {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.shift]:
print("shift key is pressed")
case [.control]:
print("control key is pressed")
case [.option] :
print("option key is pressed")
case [.command]:
print("Command key is pressed")
case [.control, .shift]:
print("control-shift keys are pressed")
case [.option, .shift]:
print("option-shift keys are pressed")
case [.command, .shift]:
print("command-shift keys are pressed")
case [.control, .option]:
print("control-option keys are pressed")
case [.control, .command]:
print("control-command keys are pressed")
case [.option, .command]:
print("option-command keys are pressed")
case [.shift, .control, .option]:
print("shift-control-option keys are pressed")
case [.shift, .control, .command]:
print("shift-control-command keys are pressed")
case [.control, .option, .command]:
print("control-option-command keys are pressed")
case [.shift, .command, .option]:
print("shift-command-option keys are pressed")
case [.shift, .control, .option, .command]:
print("shift-control-option-command keys are pressed")
default:
print("no modifier keys are pressed")
}
}
}
To get rid of the purr sound when pressing the character keys you need to subclass your view, override the method performKeyEquivalent and return true.
import Cocoa
class View: NSView {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
return true
}
}
Sample Project
Swift4
Just found a solution for the very same problem, Swift4. The idea behind that: if the pressed key was handled by a custom logic, the handler shall return nil, otherwise the (unhandled) event...
class MyViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ...
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
if self.myKeyDown(with: $0) {
return nil
} else {
return $0
}
}
}
func myKeyDown(with event: NSEvent) -> Bool {
// handle keyDown only if current window has focus, i.e. is keyWindow
guard let locWindow = self.view.window,
NSApplication.shared.keyWindow === locWindow else { return false }
switch Int( event.keyCode) {
case kVK_Escape:
// do what you want to do at "Escape"
return true
default:
return false
}
}
}
And here we are: no Purr / Funk sound when key is pressed...
[Update] Added check of keyWindow. Without this, keyDown() is fired even if another view/window contains the first responder...
I was trying to find an answer for swift 3, here is what worked for me:
Swift 3
import Cocoa
// We subclass an NSView
class MainView: NSView {
// Allow view to receive keypress (remove the purr sound)
override var acceptsFirstResponder : Bool {
return true
}
// Override the NSView keydown func to read keycode of pressed key
override func keyDown(with theEvent: NSEvent) {
Swift.print(theEvent.keyCode)
}
}
I manage to get it work from subclass of NSWindowController
class MyWindowController: NSWindowController {
override func keyDown(theEvent: NSEvent) {
print("keyCode is \(theEvent.keyCode)")
}
}
UPDATE:
import Cocoa
protocol WindowControllerDelegate {
func keyDown(aEvent: NSEvent)
}
class WindowController: NSWindowController {
var delegate: WindowControllerDelegate?
override func windowDidLoad() {
super.windowDidLoad()
delegate = window?.contentViewController as! ViewController
}
override func keyDown(theEvent: NSEvent) {
delegate?.keyDown(theEvent)
}
}
and ViewController:
class ViewController: NSViewController, WindowControllerDelegate {
#IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
override func keyDown(theEvent: NSEvent) {
textField.stringValue = "key = " + (theEvent.charactersIgnoringModifiers
?? "")
textField.stringValue += "\ncharacter = " + (theEvent.characters ?? "")
textField.stringValue += "\nmodifier = " + theEvent.modifierFlags.rawValue.description
}
}
let kLeftArrowKeyCode: UInt16 = 123
let kRightArrowKeyCode: UInt16 = 124
let kDownArrowKeyCode: UInt16 = 125
let kUpArrowKeyCode: UInt16 = 126
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case kLeftArrowKeyCode:
print("left")
break
case kRightArrowKeyCode:
print("right")
break
case kDownArrowKeyCode:
print("down")
break
case kUpArrowKeyCode:
print("up")
break
default:
print("other")
super.keyDown(with: event)
break
}
print("Key with number: \(event.keyCode) was pressed")
}
I am creating the main menu for a sprite kit application I am building. Throughout my entire project, I have used SKScenes to hold my levels and the actual gameplay. However, now I need a main menu, which holds buttons like "Play," "Levels," "Shop," etc... However, I don't feel really comfortable the way I am adding buttons now, which is like this:
let currentButton = SKSpriteNode(imageNamed: button) // Create the SKSpriteNode that holds the button
self.addChild(currentButton) // Add that SKSpriteNode to the SKScene
And I check for the touch of the button like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
for node in self.nodes(at: touchLocation) {
guard let nodeName = node.name else {
continue
}
if nodeName == ButtonLabel.Play.rawValue {
DispatchQueue.main.asyncAfter(deadline: .now()) {
let transition = SKTransition.reveal(with: .left, duration: 1)
self.view?.presentScene(self.initialLevel, transition: transition)
self.initialLevel.loadStartingLevel()
}
return
}
if nodeName == ButtonLabel.Levels.rawValue {
slideOut()
}
}
}
However, I don't know if this is considered efficient. I was thinking of using UIButtons instead, but for that would I have to use an UIView?
Or can I add UIButtons to an SKView (I don't really get the difference between an SKView, SKScene, and UIView) What is recommended for menus?
I totally agree with #Whirlwind here, create a separate class for your button that handles the work for you. I do not think the advice from #ElTomato is the right advice. If you create one image with buttons included you have no flexibility on placement, size, look and button state for those buttons.
Here is a very simple button class that is a subclass of SKSpriteNode. It uses delegation to send information back to the parent (such as which button has been pushed), and gives you a simple state change (gets smaller when you click it, back to normal size when released)
import Foundation
import SpriteKit
protocol ButtonDelegate: class {
func buttonClicked(sender: Button)
}
class Button: SKSpriteNode {
//weak so that you don't create a strong circular reference with the parent
weak var delegate: ButtonDelegate!
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(0.9)
self.delegate.buttonClicked(sender: self)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
}
}
This button can be instantiated 2 ways. You can create an instance of it in the Scene editor, or create an instance in code.
class MenuScene: SKScene, ButtonDelegate {
private var button = Button()
override func didMove(to view: SKView) {
if let button = self.childNode(withName: "button") as? Button {
self.button = button
button.delegate = self
}
let button2 = Button(texture: nil, color: .magenta, size: CGSize(width: 200, height: 100))
button2.name = "button2"
button2.position = CGPoint(x: 0, y: 300)
button2.delegate = self
addChild(button2)
}
}
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
You have to remember to make the scene conform to the delegate
class MenuScene: SKScene, ButtonDelegate
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
For simple scenes what you are doing is fine, and actually preferred because you can use the .SKS file.
However, if you have a complex scene what I like to do is subclass a Sprite and then override that node's touchesBegan.
Here is a node that I use in all of my projects... It is a simple "on off" button. I use a "pointer" to a Boolean via the custom Reference class I made, so that way this node doesn't need to be concerned with your other scenes, nodes, etc--it simply changes the value of the Bool for the other bits of code to do with what they want:
public final class Reference<T> { var value: T; init(_ value: T) { self.value = value } }
// MARK: - Toggler:
public final class Toggler: SKLabelNode {
private var refBool: Reference<Bool>
var value: Bool { return refBool.value }
var labelName: String
/*
var offText = ""
var onText = ""
*/
func toggleOn() {
refBool.value = true
text = labelName + ": on"
}
func toggleOff() {
refBool.value = false
text = labelName + ": off"
}
/*init(offText: String, onText: String, refBool: Reference<Bool>) {
ref = refBool
super.init(fontNamed: "Chalkduster")
if refBool.value { toggleOn() } else { toggleOff() }
isUserInteractionEnabled = true
}
*/
init(labelName: String, refBool: Reference<Bool>) {
self.refBool = refBool
self.labelName = labelName
super.init(fontNamed: "Chalkduster")
isUserInteractionEnabled = true
self.refBool = refBool
self.labelName = labelName
if refBool.value { toggleOn() } else { toggleOff() }
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if refBool.value { toggleOff() } else { toggleOn() }
}
public required init?(coder aDecoder: NSCoder) { fatalError("") }
override init() {
self.refBool = Reference<Bool>(false)
self.labelName = "ERROR"
super.init()
}
};
This is a more elaborate button than say something that just runs a bit of code when you click it.
The important thing here is that if you go this route, then you need to make sure to set the node's .isUserInteractionEnabled to true or it will not receive touch input.
Another suggestion along the lines of what you are doing, is to separate the logic from the action:
// Outside of touches func:
func touchPlay() {
// Play code
}
func touchExit() {
// Exit code
}
// In touches began:
guard let name = node.name else { return }
switch name {
case "play": touchPlay()
case "exit": touchExit()
default:()
}
PS:
Here is a very basic example of how to use Toggler:
class Scene: SKScene {
let spinnyNode = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
// This is the reference type instance that will be stored inside of our Toggler instance:
var shouldSpin = Reference<Bool>(true)
override func didMove(to view: SKView) {
addChild(spinnyNode)
spinnyNode.run(.repeatForever(.rotate(byAngle: 3, duration: 1)))
let toggleSpin = Toggler(labelName: "Toggle Spin", refBool: shouldSpin)
addChild(toggleSpin)
toggleSpin.position.y += 100
}
override func update(_ currentTime: TimeInterval) {
if shouldSpin.value == true {
spinnyNode.isPaused = false
} else if shouldSpin.value == false {
spinnyNode.isPaused = true
}
}
}
Currently I am having multiple textfields in a view. If the user taps at one of them there should be a function responding to the event. Is there a way on how to do react (if a textfield got the focus)? I tried it with the NSTextFieldDelegate method but there is no appropriate function for this event.
This is how my code looks at the moment:
class ViewController: NSViewController, NSTextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let textField = NSTextField(frame: CGRectMake(10, 10, 37, 17))
textField.stringValue = "Label"
textField.bordered = false
textField.backgroundColor = NSColor.controlColor()
view.addSubview(textField)
textField.delegate = self
let textField2 = NSTextField(frame: CGRectMake(30, 30, 37, 17))
textField2.stringValue = "Label"
textField2.bordered = false
textField2.backgroundColor = NSColor.controlColor()
view.addSubview(textField2)
textField2.delegate = self
}
func control(control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool {
print("working") // this only works if the user enters a charakter
return true
}
}
The textShouldBeginEditing function only handles the event if the user tries to enter a character but this isn't what I want. It has to handle the event if he clicks on the textfield.
Any ideas, thanks a lot?
Edit
func myAction(sender: NSView)
{
print("aktuell: \(sender)")
currentObject = sender
}
This is the function I want to call.
1) Create a subclass of NSTextField.
import Cocoa
class MyTextField: NSTextField {
override func mouseDown(theEvent:NSEvent) {
let viewController:ViewController = ViewController()
viewController.textFieldClicked()
}
}
2) With Interface building, select the text field you want to have a focus on. Navigate to Custom Class on the right pane. Then set the class of the text field to the one you have just created.
3) The following is an example for ViewController.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
4) Adding text fields programmatically...
import Cocoa
class ViewController: NSViewController {
let myField:MyTextField = MyTextField()
override func viewDidLoad() {
super.viewDidLoad()
//let myField:MyTextField = MyTextField()
myField.setFrameOrigin(NSMakePoint(20,70))
myField.setFrameSize(NSMakeSize(120,22))
let textField:NSTextField = NSTextField()
textField.setFrameOrigin(NSMakePoint(20,40))
textField.setFrameSize(NSMakeSize(120,22))
self.view.addSubview(myField)
self.view.addSubview(textField)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
I know it’s been answered some while ago but I did eventually find this solution for macOS in Swift 3 (it doesn’t work for Swift 4 unfortunately) which notifies when a textfield is clicked inside (and for each key stroke).
Add this delegate to your class:-
NSTextFieldDelegate
In viewDidLoad() add these:-
imputTextField.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: Notification.Name.NSTextViewDidChangeSelection, object: nil)
Then add this function:-
func textDidChange(_ notification: Notification) {
print("Its come here textDidChange")
guard (notification.object as? NSTextView) != nil else { return }
let numberOfCharatersInTextfield: Int = textFieldCell.accessibilityNumberOfCharacters()
print("numberOfCharatersInTextfield = \(numberOfCharatersInTextfield)")
}
Hope this helps others.
I made a game using SpriteKit and Xcode 7 beta. I tried to add GameCenter and Leaderboard but the problem is that the score in the leaderboard won't change (HighScore doesn't upload to GameCenter), It stay 0 all the time and I don't know how to fix it. I'm using 2 different files: GameViewController.swift, and PointsLabel.swift
GameViewController.swift:
import GameKit
class GameViewController: UIViewController,UIGestureRecognizerDelegate, GKGameCenterControllerDelegate {
var score: PointsLabel!
override func viewDidLoad() {
super.viewDidLoad()
//initiate gamecenter
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(GameViewController, error) -> Void in
if (GameViewController != nil) {
self.presentViewController(GameViewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
}
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(score)
showLeader()
}
//send high score to leaderboard
func saveHighscore(score:Int) {
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "Leaderboard_01")
scoreReporter.value = Int64(score)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("error")
}
})
}
}
//shows leaderboard screen
func showLeader() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
}
//hides leaderboard screen
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
There is an Error in this file:
Cannot invoke 'saveHighscore' with an argument list of type '(PointsLabel!)'
on code:
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(score) //<- Here is Error
showLeader()
}
PointsLabel.swift:
import Foundation
import UIKit
import SpriteKit
class PointsLabel: SKLabelNode {
var score:Int = 0
init(num: Int) {
super.init()
fontColor = UIColor.blackColor()
fontSize = 30.0
score = num
text = "\(num)"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func increment() {
score++
text = "\(score)"
}
func setTo(num: Int) {
self.score = num
text = "\(self.score)"
}
}
I don't know how to fix it!
Your score variable is of type PointsLabel but your saveHighscore function expects an Int as parameter.
Looking at your code, the variable score will be an instance of PointsLabel so I guess you could use the score property of your instanciated class, which is an Int (the fact that you used "score" as a name for both variables is confusing. I suggest changing names to make them more explicit.).
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(score.score)
showLeader()
}