@selector () em Swift?

660

Estou tentando criar um NSTimerarquivo, Swiftmas estou tendo alguns problemas.

NSTimer(timeInterval: 1, target: self, selector: test(), userInfo: nil, repeats: true)

test() é uma função na mesma classe.


Eu recebo um erro no editor:

Não foi possível encontrar uma sobrecarga para 'init' que aceita os argumentos fornecidos

Quando eu mudo selector: test()para selector: nilo erro desaparece.

Eu tentei:

  • selector: test()
  • selector: test
  • selector: Selector(test())

Mas nada funciona e não consigo encontrar uma solução nas referências.

Arbitur
fonte
11
selector: test()chamaria teste passaria seu valor de retorno ao selectorargumento.
Michael Dorst

Respostas:

930

O Swift em si não usa seletores - vários padrões de design que no Objective-C utilizam seletores funcionam de maneira diferente no Swift. (Por exemplo, use encadeamento opcional em tipos de protocolo ou is/ astestes em vez de respondsToSelector:e use fechamentos sempre que puder, em vez deperformSelector: obter uma melhor segurança de tipo / memória.)

Mas ainda existem várias APIs importantes baseadas em ObjC que usam seletores, incluindo temporizadores e o padrão de destino / ação. Swift fornece o Selectortipo para trabalhar com eles. (O Swift usa isso automaticamente no lugar do SELtipo da ObjC .)

No Swift 2.2 (Xcode 7.3) e posterior (incluindo Swift 3 / Xcode 8 e Swift 4 / Xcode 9):

Você pode construir a Selectorpartir de um tipo de função Swift usando a #selectorexpressão

let timer = Timer(timeInterval: 1, target: object,
                  selector: #selector(MyClass.test),
                  userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
                 for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
             with: button, with: otherButton)

A melhor coisa dessa abordagem? Uma referência de função é verificada pelo compilador Swift, para que você possa usar a #selectorexpressão apenas com pares de classe / método que realmente existem e são elegíveis para uso como seletores (consulte "Disponibilidade do seletor" abaixo). Você também pode fazer sua referência de função apenas tão específica quanto necessário, conforme as regras do Swift 2.2+ para nomeação de tipo de função .

(Na verdade, isso é uma melhoria em relação à @selector()diretiva da ObjC , porque a -Wundeclared-selectorverificação do compilador verifica apenas se o seletor nomeado existe. A referência da função Swift que você passa para#selector verifica a existência, associação a uma classe e assinatura de tipo.)

Existem algumas advertências extras para as referências de função que você passa para a #selectorexpressão:

  • Várias funções com o mesmo nome de base podem ser diferenciadas por seus rótulos de parâmetro, usando a sintaxe mencionada acima para referências de função (por exemplo, insertSubview(_:at:)vs insertSubview(_:aboveSubview:)). Mas se uma função não possui parâmetros, a única maneira de desambiguar é usar uma asconversão com a assinatura de tipo da função (por exemplo, foo as () -> ()vs foo(_:)).
  • Há uma sintaxe especial para os pares getter / setter de propriedades no Swift 3.0+. Por exemplo, dado a var foo: Int, você pode usar #selector(getter: MyClass.foo)ou #selector(setter: MyClass.foo).

Notas gerais:

Casos em #selectorque não funciona e nomeação: Às vezes você não tem uma referência de função para fazer um seletor (por exemplo, com métodos registrados dinamicamente no tempo de execução ObjC). Nesse caso, você pode construir a Selectorpartir de uma string: por exemplo Selector("dynamicMethod:")- embora você perca a verificação de validade do compilador. Ao fazer isso, você precisa seguir as regras de nomenclatura do ObjC, incluindo dois pontos ( :) para cada parâmetro.

Disponibilidade do seletor: O método referenciado pelo seletor deve ser exposto ao tempo de execução do ObjC. No Swift 4, todo método exposto ao ObjC deve ter sua declaração precedida pelo @objcatributo (Nas versões anteriores, você obtinha esse atributo gratuitamente em alguns casos, mas agora precisa declará-lo explicitamente.)

Lembre-se de que os privatesímbolos também não estão expostos ao tempo de execução - seu método precisa ter pelo menos internalvisibilidade.

Caminhos principais: estão relacionados, mas não são exatamente os mesmos, dos seletores. Também existe uma sintaxe especial no Swift 3: por exemplo chris.valueForKeyPath(#keyPath(Person.friends.firstName)). Veja SE-0062 para detalhes. E ainda mais KeyPathcoisas no Swift 4 , verifique se você está usando a API correta baseada em KeyPath em vez de seletores, se apropriado.

Você pode ler mais sobre seletores em Interagindo com APIs do Objective-C em Usando Swift with Cocoa e Objective-C .

Nota: Antes do Swift 2.2, em Selectorconformidade com StringLiteralConvertible, para que você possa encontrar o código antigo em que cadeias simples são passadas para APIs que usam seletores. Você deseja executar "Converter para a sintaxe Swift atual" no Xcode para obter os que estão usando #selector.

rickster
fonte
8
Colocando uma string com o nome da função funcionando, o NSSelectorFromString () também funciona.
Arbitur
7
Gostaria de mencionar que, embora "Interagindo com APIs do Objective-C" esteja no site, NÃO está no livro 'The Swift Programming Language'.
10
Provavelmente, isso deve mencionar que o seletor precisa de um ":" no final, se houver um argumento. (Por exemplo, test () -> "test" & test (this: String) -> "test:") #
7267 Daniel Schlaug
2
Também deve ser destacado que as estruturas do Cocoa esperam um nome de método no estilo Objective-C. Se o seu método usa um argumento, você precisará de um ':' se size:andShape:WithinitWithData:func init(Data data: NSData)
receber
6
Existe alguma maneira de adicionar validação ao passar o "seletor" como uma string? O compilador do IE nos avisa quando
cometemos
86

Aqui está um exemplo rápido de como usar a Selectorclasse no Swift:

override func viewDidLoad() {
    super.viewDidLoad()

    var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))
    self.navigationItem.rightBarButtonItem = rightButton
}

func method() {
    // Something cool here   
}

Observe que, se o método passado como uma string não funcionar, ele falhará no tempo de execução, não no tempo de compilação e travará o aplicativo. Seja cuidadoso

Oscar Swanros
fonte
13
o que é horrível ... existe um tipo de coisa "NSStringFromSelector"?
Lena Bru
11
não posso acreditar que eles projetaram para seletores não verificadas desde objc tinha essa
malhal
4
@malcomhall: @selectoré útil, mas não é aplicado de maneira tão formal quanto você imagina. "Seletor não declarado" é apenas um aviso do compilador, porque novos seletores sempre podem ser introduzidos em tempo de execução. Porém, referências ao seletor verificáveis ​​/ refatoráveis ​​no Swift seriam uma boa solicitação de recurso .
Rickster 8/08
2
Esta resposta é útil, mas a resposta abaixo com @objc é mais apropriada.
precisa
Ao passar a string do seletor como uma variável ou parâmetro, você precisará informar o compilador como um seletor usando a função Selector (). obrigado
levous
46

Além disso, se sua classe (Swift) não descender de uma classe Objective-C, você deverá ter dois pontos no final da cadeia de nome do método de destino e deverá usar a propriedade @objc com seu método de destino, por exemplo

var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))

@objc func method() {
    // Something cool here   
} 

caso contrário, você receberá um erro "Seletor não reconhecido" em tempo de execução.

user3771857
fonte
3
1. os seletores com dois pontos devem aceitar um argumento 2. de acordo com as ações do cronômetro da Apple docs, devem aceitar o argumento NSTimer 3. a Selectorpalavra-chave não é obrigatória. Portanto, neste caso, a assinatura deve ser@objc func method(timer: NSTimer) {/*code*/}
Yevhen Dubinin
@objctrabalhou para mim. Não precisei incluir timer: NSTimerna minha assinatura de método para que ela fosse chamada.
Mike Taverne
32

Atualização Swift 2.2+ e Swift 3

Use a nova #selectorexpressão, que elimina a necessidade de usar literais de string, tornando o uso menos propenso a erros. Para referência:

Selector("keyboardDidHide:")

torna-se

#selector(keyboardDidHide(_:))

Veja também: Proposta de evolução rápida

Nota (Swift 4.0):

Se estiver usando, #selectorvocê precisará marcar a função como@objc

Exemplo:

@objc func something(_ sender: UIButton)

Kyle Clegg
fonte
26

Swift 4.0

você cria o seletor como abaixo.

1. adicione o evento a um botão como:

button.addTarget(self, action: #selector(clickedButton(sender:)), for: UIControlEvents.touchUpInside)

e a função será como abaixo:

@objc func clickedButton(sender: AnyObject) {

}
pravin salame
fonte
4
Você esqueceu de colocar @objcantes de funcque é exigido em Swift 4.
Vahid Amiri
24

Para futuros leitores, descobri que estava com um problema e estava recebendo um unrecognised selector sent to instanceerro causado pela marcação do destino funccomo privado.

O func DEVE estar publicamente visível para ser chamado por um objeto com uma referência a um seletor.

Rob Sanders
fonte
13
não precisa ser público, você ainda pode manter o método privado, mas adicioná- objclo antes da declaração. Ex: @objc private func foo() { ...então você pode usar "foo"como um seletor tanto você gosta
apouche
Também pode ser internal, portanto, não especificando nenhum modificador de acesso. Costumo usar este padrão://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
Sajjon
19

Caso outra pessoa tenha o mesmo problema que eu tive com o NSTimer, onde nenhuma das outras respostas resolveu o problema, é realmente importante mencionar que, se você estiver usando uma classe que não herda do NSObject, direta ou profundamente na hierarquia ( por exemplo, arquivos rápidos criados manualmente), nenhuma das outras respostas funcionará mesmo quando especificada da seguinte forma:

let timer = NSTimer(timeInterval: 1, target: self, selector: "test", 
                    userInfo: nil, repeats: false)
func test () {}

Sem alterar nada além de apenas herdar a classe do NSObject, parei de receber o erro "Seletor não reconhecido" e fiz com que minha lógica funcionasse conforme o esperado.

Martin Cazares
fonte
O problema dessa alternativa é que você não pode alterar uma classe (digamos, ViewController) para herdar do NSObject, já que você precisa do material implementado da classe ViewController (por exemplo, viewDidLoad ()). Alguma idéia de como chamar uma função Swift dentro de um ViewController usando NSTimer ... e?
eharo2
1
UIViewController já herda de NSObject, a maioria das classes expostos pelo SDK fazer, este exemplo é para suas próprias classes criadas que exigem funcionalidade NSTimer ...
Martin Cazares
14

Se você deseja passar um parâmetro para a função do NSTimer, aqui está sua solução:

var somethingToPass = "It worked"

let timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "tester:", userInfo: somethingToPass, repeats: false)

func tester(timer: NSTimer)
{
    let theStringToPrint = timer.userInfo as String
    println(theStringToPrint)
}

Inclua os dois pontos no texto do seletor (testador :) e seus parâmetros vão em userInfo.

Sua função deve usar o NSTimer como parâmetro. Em seguida, basta extrair userInfo para obter o parâmetro que passou.

Lambreta
fonte
3
Eu estava usando NSTimer (0.01, target: self, ...), que NÃO funcionou, enquanto NSTimer.scheduledTimerWithTimeInterval (0.01, ..) funcionou !? Estranho, mas obrigado @Scooter pela resposta!
iOS-Coder
1
@ iOS-Coder apenas criar um temporizador com o initialiser não adicioná-lo a um RunLoop, enquanto que scheduledTimerWith...automaticamente adiciona à RunLoop atual - portanto, não há comportamento estranho aqui em tudo;)
David Ganster
1
@ David obrigado por sua sugestão. Acho que meu mal-entendido deve pertencer à categoria STFW ou RTFA (Read The F ... ing API)?
iOS-Coder
1
Não se preocupe com isso, ninguém pode ser esperado para ler a documentação sobre cada método único em cada API;)
David Ganster
10

Seletores são uma representação interna de um nome de método em Objective-C. No Objective-C, "@selector (methodName)" converteria um método de código-fonte em um tipo de dados SEL. Como você não pode usar a sintaxe @selector no Swift (o rickster está no ponto), você deve especificar manualmente o nome do método como um objeto String diretamente ou passando um objeto String para o tipo Selector. Aqui está um exemplo:

var rightBarButton = UIBarButtonItem(
    title: "Logout", 
    style: UIBarButtonItemStyle.Plain, 
    target: self, 
    action:"logout"
)

ou

var rightBarButton = UIBarButtonItem(
    title: "Logout", 
    style: UIBarButtonItemStyle.Plain, 
    target: self, 
    action:Selector("logout")
)
Jon Tsiros
fonte
8

Swift 4.1
Com amostra de gesto de toque

let gestureRecognizer = UITapGestureRecognizer()
self.view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.addTarget(self, action: #selector(self.dismiss(completion:)))

// Use destination 'Class Name' directly, if you selector (function) is not in same class.
//gestureRecognizer.addTarget(self, action: #selector(DestinationClass.dismiss(completion:)))


@objc func dismiss(completion: (() -> Void)?) {
      self.dismiss(animated: true, completion: completion)
}

Consulte o documento da Apple para obter mais detalhes sobre: Expressão do seletor

Krunal
fonte
Por favor, pare de fazer isso. Não ajuda ninguém. Como isso é diferente do Swift 3.1? E por que você achou necessário adicionar outra resposta a esta quando ela já possui cerca de 20 respostas?
Fogmeister
o seletor de chamadas é diferente no swift 4. Tente estas respostas no swift 4 e veja. Nenhuma delas funcionará sem edição. Por favor, não marcar qualquer declaração como spam sem garantir a sua impotance
Krunal
Existe algum motivo para você não conseguir editar a resposta aceita e existente? Isso o tornaria realmente útil, em vez de adicionar ao final de uma longa lista de respostas. O botão "Editar" está lá por um motivo.
Fogmeister
Além disso, qual parte disso é diferente do Swift 3?
Fogmeister
2
Você deve adicionar a tag objc a qualquer seletor do Swift 4. Esta é a resposta correta. E você não deve editar as respostas de outras pessoas para mudar seu significado. @Krunal está totalmente certo.
Unome
6
// for swift 2.2
// version 1
buttton.addTarget(self, action: #selector(ViewController.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(ViewController.tappedButton2(_:)), forControlEvents: .TouchUpInside)

// version 2
buttton.addTarget(self, action: #selector(self.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(self.tappedButton2(_:)), forControlEvents: .TouchUpInside)

// version 3
buttton.addTarget(self, action: #selector(tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(tappedButton2(_:)), forControlEvents: .TouchUpInside)

func tappedButton() {
  print("tapped")
}

func tappedButton2(sender: UIButton) {
  print("tapped 2")
}

// swift 3.x
button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)

func tappedButton(_ sender: UIButton) {
  // tapped
}

button.addTarget(self, action: #selector(tappedButton(_:_:)), for: .touchUpInside)

func tappedButton(_ sender: UIButton, _ event: UIEvent) {
  // tapped
}
John Lee
fonte
teria sido melhor e mais educativo se você tivesse um exemplo usando dois ou três argumentos para Swift3 ou Swift4 também. Obrigado.
nyxee
5
Create Refresh control using Selector method.   
    var refreshCntrl : UIRefreshControl!
    refreshCntrl = UIRefreshControl()
    refreshCntrl.tintColor = UIColor.whiteColor()
    refreshCntrl.attributedTitle = NSAttributedString(string: "Please Wait...")
    refreshCntrl.addTarget(self, action:"refreshControlValueChanged", forControlEvents: UIControlEvents.ValueChanged)
    atableView.addSubview(refreshCntrl)

// Atualizar método de controle

func refreshControlValueChanged(){
    atableView.reloadData()
    refreshCntrl.endRefreshing()

}
Renish Dadhaniya
fonte
5

Como o Swift 3.0 é publicado, é ainda mais sutil declarar uma targetAction apropriada

class MyCustomView : UIView {

    func addTapGestureRecognizer() {

        // the "_" is important
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyCustomView.handleTapGesture(_:)))
        tapGestureRecognizer.numberOfTapsRequired = 1
        addGestureRecognizer(tapGestureRecognizer)
    }

    // since Swift 3.0 this "_" in the method implementation is very important to 
    // let the selector understand the targetAction
    func handleTapGesture(_ tapGesture : UITapGestureRecognizer) {

        if tapGesture.state == .ended {
            print("TapGesture detected")
        }
    }
}
LukeSideWalker
fonte
4

Ao usar performSelector()

/addtarget()/NStimer.scheduledTimerWithInterval() métodos, seu método (correspondente ao seletor) deve ser marcado como

@objc
For Swift 2.0:
    {  
        //...
        self.performSelector(“performMethod”, withObject: nil , afterDelay: 0.5)
        //...


    //...
    btnHome.addTarget(self, action: “buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
    //...

    //...
     NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector : “timerMethod”, userInfo: nil, repeats: false)
    //...

}

@objc private func performMethod() {}
@objc private func buttonPressed(sender:UIButton){.
}
@objc private func timerMethod () {.
}

Para o Swift 2.2, você precisa escrever '#selector ()' em vez do nome da string e do seletor, para que as possibilidades de erro ortográfico e falha devido a isso não existam mais. Abaixo está um exemplo

self.performSelector(#selector(MyClass.performMethod), withObject: nil , afterDelay: 0.5)
sschunara
fonte
3

você cria o seletor como abaixo.
1

UIBarButtonItem(
    title: "Some Title",
    style: UIBarButtonItemStyle.Done,
    target: self,
    action: "flatButtonPressed"
)

2)

flatButton.addTarget(self, action: "flatButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)

Observe que a sintaxe @selector desapareceu e foi substituída por uma String simples, nomeando o método a ser chamado. Há uma área em que todos podemos concordar que a verbosidade atrapalhou. Obviamente, se declaramos que existe um método de destino chamado flatButtonPressed: é melhor escrevermos um:

func flatButtonPressed(sender: AnyObject) {
  NSLog("flatButtonPressed")
}

ajuste o temporizador:

    var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, 
                    target: self, 
                    selector: Selector("flatButtonPressed"), 
                    userInfo: userInfo, 
                    repeats: true)
    let mainLoop = NSRunLoop.mainRunLoop()  //1
    mainLoop.addTimer(timer, forMode: NSDefaultRunLoopMode) //2 this two line is optinal

Para ser completo, aqui está o flatButtonPressed

func flatButtonPressed(timer: NSTimer) {
}
Daxesh Nagar
fonte
Você tem alguma fonte para " Observe que a sintaxe do @selector se foi "?
2141818
3

Achei muitas dessas respostas úteis, mas não estava claro como fazer isso com algo que não era um botão. Eu estava adicionando um reconhecedor de gestos a um UILabel rapidamente e com dificuldades, então aqui está o que eu achei que funcionou para mim depois de ler tudo acima:

let tapRecognizer = UITapGestureRecognizer(
            target: self,
            action: "labelTapped:")

Onde o "Seletor" foi declarado como:

func labelTapped(sender: UILabel) { }

Observe que é público e que eu não estou usando a sintaxe Selector (), mas também é possível fazer isso.

let tapRecognizer = UITapGestureRecognizer(
            target: self,
            action: Selector("labelTapped:"))
cynistersix
fonte
3

O uso de #selector verificará seu código no momento da compilação para garantir que o método que você deseja chamar realmente exista. Melhor ainda, se o método não existir, você receberá um erro de compilação: o Xcode se recusará a criar seu aplicativo, banindo assim o esquecimento de outra possível fonte de erros.

override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.rightBarButtonItem =
            UIBarButtonItem(barButtonSystemItem: .Add, target: self,
                            action: #selector(addNewFireflyRefernce))
    }

    func addNewFireflyReference() {
        gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
    }
Desenvolvedor Swift
fonte
2

Pode ser útil observar onde você configura o controle que aciona a ação.

Por exemplo, descobri que, ao configurar um UIBarButtonItem, tive que criar o botão no viewDidLoad, caso contrário, obteria uma exceção de seletor não reconhecida.

override func viewDidLoad() {
    super.viewDidLoad() 

    // add button
    let addButton = UIBarButtonItem(image: UIImage(named: "746-plus-circle.png"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("addAction:"))
    self.navigationItem.rightBarButtonItem = addButton
}

func addAction(send: AnyObject?) {     
    NSLog("addAction")
}
Michael Peterson
fonte
1

Altere como uma nomeação simples de string no método que chama a sintaxe do seletor

var timer1 : NSTimer? = nil
timer1= NSTimer(timeInterval: 0.1, target: self, selector: Selector("test"), userInfo: nil, repeats: true)

Depois disso, digite func test ().

Thar Htet
fonte
1

selectoré uma palavra de Objective-Cmundo e você é capaz de usá-lo de Swiftter a possibilidade de chamada Objective-Ca partir Swift Ele permite que você execute algum código em tempo de execução

Antes Swift 2.2da sintaxe:

Selector("foo:")

Como um nome de função é passado Selectorcomo um parâmetro String ("foo"), não é possível verificar um nome em tempo de compilação . Como resultado, você pode receber um erro de tempo de execução:

unrecognized selector sent to instance

Depois que Swift 2.2+a sintaxe é:

#selector(foo(_:))

O preenchimento automático do Xcode ajuda você a chamar o método certo

yoAlex5
fonte
0

Para Swift 3

// Exemplo de código para criar timer

Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)

WHERE
timeInterval:- Interval in which timer should fire like 1s, 10s, 100s etc. [Its value is in secs]
target:- function which pointed to class. So here I am pointing to current class.
selector:- function that will execute when timer fires.

func updateTimer(){
    //Implemetation 
} 

repeats:- true/false specifies that timer should call again n again.
CrazyPro007
fonte
0

Seletor no Swift 4:

button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
Mahesh Chaudhari
fonte
-1

Para rápido 3

let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.test), userInfo: nil, repeats: true)

Declaração de função Na mesma classe:

@objc func test()
{
    // my function
} 
Mustajab Haider
fonte
Se o alvo for próprio, não será necessário ter selfno seletor. Isso deve ser suficiente:let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
Mithra Singam