Calling a function inside a function? - swift

I have the following function (which uses this awesome library) for a button that captures data (copies data) from a cell and then tries to call another function if the user taps imgtagAction. The first button func buttonViewLinkAction works great. I get the AlertView and I'm presented with another button imgtagAction. However, when I click on that button I get:
unrecognized selector sent to instance.
//get buttonViewLinkAction and copy to pasteboard
#IBAction func buttonViewLinkAction(sender: UIButton) {
print("buttonViewLinkAction tapped")
let face = self.faces[sender.tag]
if let imageNAME: String = String(face.name){
print(imageNAME .uppercaseString)
}
if let imageURL = NSURL(string:face.image) {
print(imageURL)
}
UIPasteboard.generalPasteboard().string = face.directLink
let alertView = SCLAlertView()
alertView.addButton("Add [imag] tags", target:self, selector:Selector("imgtagAction:"))
alertView.showSuccess((face.name), subTitle: "Direct link copied to clipboard")
func imgtagAction(Sender: AnyObject) {
print("imgtagAction tapped")
UIPasteboard.generalPasteboard().string = "[img]" + face.directLink + "[/img]"
}
}
So, when I move the imgtagAction function outside of the buttonViewLinkAction function I can't get access to the cell data.
func imgtagAction(Sender: AnyObject) {
print("imgtagAction tapped")
let face = self.faces[sender.tag]
if let imageNAME: String = String(face.name){
print(imageNAME .uppercaseString)
}
if let imageURL = NSURL(string:face.image) {
print(imageURL)
}
UIPasteboard.generalPasteboard().string = "[img]" + face.directLink + "[/img]"
}
The error I get is:
use of unresolved identifier 'sender'.
What am I doing wrong here?

You have Sender instead of sender. This is what your function should look like.
func imgtagAction(sender: AnyObject)
{
print("imgtagAction tapped")
let face = self.faces[sender.tag]
if let imageNAME: String = String(face.name)
{
print(imageNAME .uppercaseString)
}
if let imageURL = NSURL(string:face.image)
{
print(imageURL)
}
UIPasteboard.generalPasteboard().string = "[img]" + face.directLink + "[/img]"
}

Related

Trying to make a backspace button programmatically to do the same as the keyboard delete (ᗏX) button

#objc func searchButtonTap() {
searchBar.becomeFirstResponder()
var remove = searchBar.text
remove = String(remove!.dropLast())
searchBar.text = remove
}
Also tried two backspaces
#objc func searchButtonTap() {
let name: String = searchBar.text!
let endIndex = name.index(name.endIndex, offsetBy: -2)
let truncated = String(name[..<endIndex])
searchBar.text = truncated
}
Assuming that you want to remove the last character of the search bar text when the user taps on back(search?) button.
So,
#objc func searchButtonTap() {
guard !(searchBar.text?.isEmpty ?? true) else { return } //Making sure that you don't hit the "Fatal error: Can't remove more items from a collection than it contains"
searchBar.text?.removeLast(1)
}

Download single Object of Firestore and save it into an struct/class object

I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not

Can't write string to file inside of NSSavePanel "ok" function

For some reason, I can only write a string to a file outside of NSSavePanel's ok function. I need to write it as soon as the user says "OK, I do want to save that".
Here is my code:
//An IBAction that connects to the "Save" menu item.
#IBAction func SaveButton(_ sender: Any) {
os_log("Save button pressed.")
//Declares savePanel to be equal to NSSavePanel opens the save panel in a seperate window.
let savePanel = NSSavePanel()
savePanel.runModal()
let textEntryController = EntryViewController()
//Sets a placeholder of the text we're going to write.
func ok(_ sender: Any?){
let entryPath = savePanel.url
let entryFieldContents = textEntryController.entryTextField!;
let entryText = (entryFieldContents.textStorage as NSAttributedString?)?.string
let entryContent = entryText
do {
try entryContent?.write(to: entryPath!, atomically: true, encoding: String.Encoding.utf8)
} catch {
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
}
}
Instead of using runModal() you should use the more modern closure syntax:
let savePanel = NSSavePanel()
savePanel.begin { (response) in
if response == .OK {
// write it here
}
}

Return #objc function swift [duplicate]

I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}

Game center Authentication not working

My Game Center Authentication is not working. When I build and run, it won't show my username.. has signed in. Also, when I try to add my score I get a screen that says "no data availible". Heres my code.
override func viewDidLoad() {
super.viewDidLoad()
gcAuthPlayer()
}
#IBAction func GCButton(sender: AnyObject) {
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScore(GameScene().currentScore)
}
}
func showLeaderBoard(){
let viewController = self.view.window?.rootViewController
let gcvc = GKGameCenterViewController()
gcvc.gameCenterDelegate = self
viewController?.presentViewController(gcvc, animated: true, completion: nil)
}
func saveHighScore(number: Int){
if GKLocalPlayer.localPlayer().authenticated{
let scoreReporter = GKScore(leaderboardIdentifier: "myleaderboard")
scoreReporter.value = Int64(number)
let scoreArray : [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: nil)
}
}
func gcAuthPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {
(view, error) in
if view != nil{
self.presentViewController(view!, animated: true, completion: nil)
}else{
print(GKLocalPlayer.localPlayer().authenticated)
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
This code makes no sense
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScor
You are creating a new instance of GameScene everytime you try to update the score and therefore your score is nil
I would need to see some more code but for now you need to change the score property in your game scene. For example make it a static property so you can get it in other classes.
class GameScene: SKScene {
static var currentScore = 0
static var highscoreNumber = 0
}
Than in your Scenes or ViewController you can get it like so
GameScene.currentScore = 5
GameScene.highscoreNumber = 5
Just remember that you have to reset the score to 0 everytime you restart your gameScene because it a static property.
GameScene.currentScore = 0
GameScene.highscoreNumber = 0
Than your code to post the score should look like this
saveHighScore(GameScene.highScoreNumer)
showLeaderBoard()
if GameScene.currentScore > GameScene.highScoreNumer{
saveHighScor
Your score reporting code should also handle the error and actually do the completion handler. So change it to something like this.
/// Save leaderboard progress
func reportLeaderboardProgress(value: Int, leaderboardID: String) {
let scoreReporter = GKScore(leaderboardIdentifier: leaderboardID)
scoreReporter.value = Int64(value)
GKScore.reportScores([scoreReporter]) { error in // Trailing Closure syntax
if let error = error {
print(error.localizedDescription)
return
}
print("Reported leaderboard progress \(value) to leaderboardID \(leaderboardID)")
}
}
It is also a good idea to move that code into another class to keep your overall code cleaner and more reusable.
For a nice and simple example check this helper on gitHub.
https://github.com/jackcook/GCHelper
Let me know how it goes.