O seguinte problema ocorre no Xcode 16.2, iOS18 SDK com destino de compilação iOS17
Tenho uma extensão UIBarButtonItem
para dar suporte a fechamentos em vez de meta/ação:
extension UIBarButtonItem {
typealias Closure = () -> Void
private class UIBarButtonItemClosureWrapper: NSObject {
let closure: Closure
init(_ closure: @escaping Closure) {
self.closure = closure
}
deinit {
print("DEINIT")
}
}
private enum AssociatedKeys {
static var targetClosure = 1
}
convenience init(title: String?, style: UIBarButtonItem.Style, closure: @escaping Closure) {
self.init(title: title, style: style, target: nil, action: #selector(closureAction))
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, UIBarButtonItemClosureWrapper(closure), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
convenience init(image: UIImage?, style: UIBarButtonItem.Style, closure: @escaping Closure) {
self.init(image: image, style: style, target: nil, action: #selector(closureAction))
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, UIBarButtonItemClosureWrapper(closure), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
@objc
func closureAction() {
guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? UIBarButtonItemClosureWrapper else { return }
closureWrapper.closure()
}
}
Entretanto, algo muito estranho está acontecendo e não sei por quê.
Eu uso isso em um UIViewController
in navigationItem.rightBarButtonItems
, e esse viewcontroller é o viewcontroller raiz de um UINavigationController
. Tudo funciona bem, até eu adicionar um TextField
ao SwiftUI View
que está contido no viewcontroller filho desse viewcontroller UIHostingController
.
Então, em resumo:
UINavigationController -> UIViewController -> UIHostingController -> TextField
Assim que dou o foco para o TextField
, o botão não funciona mais. Você pode vê-lo ficar translúcido conforme você toca nele (então ele está registrando o toque), mas o fechamento não é mais chamado. Se eu colocar um botão próximo a ele que use o sistema de alvo/ação padrão, esse continua funcionando.
Este é o UIViewController
código:
override func viewDidLoad() {
super.viewDidLoad()
let item = UIBarButtonItem(title: "TargetAction", style: .done, target: self, action: #selector(buttonTapped))
let item2 = UIBarButtonItem(title: "ClosureAction", style: .done) {
print("CLOSURE TEST")
}
self.navigationItem.rightBarButtonItems = [item, item2]
}
@objc func buttonTapped() {
print("TARGET TEST")
}
e este é o View
código:
struct MyView: View {
var body: some View {
Form {
TextField("name", text: .constant("text"))
}
}
Target Test
é impresso toda vez que eu toco no botão. Closure Test
é impresso quando eu toco no outro botão, até que eu foco o TextField tocando nele. Daí em diante Target Test
ainda funciona, mas Closure Test
não imprime mais.
O foco do TextField e a aparência do teclado estão fazendo algo com a barra de navegação nos bastidores que eu não sei, destruindo assim os objetos associados de alguma forma? Deinit
nunca é impresso, e colocar um ponto de interrupção ClosureAction
mostra que a função também não é chamada.
Você usou
nil
comotarget:
parâmetro ao criar umUIBarButtonItem
. Isso significa que quando o botão é pressionado, o seletor será enviado para baixo na cadeia de resposta, até que algum objeto responda ao seletor.Quando o campo de texto estiver focado, ele será o primeiro a responder, e a frente da cadeia de resposta provavelmente contém muito SwiftUI-machinary. É provável que uma dessas coisas SwiftUI ao longo da cadeia de resposta tenha "engolido" o seletor, impedindo-o de alcançar o
UIBarButtonItem
.Se você usar
self
comotarget:
, o seletor será enviado diretamente para oUIBarButtonItem
e isso funcionará como esperado.Dito isso,
UIBarButtonItem
o s suporta ações baseadas em fechamento desde o iOS 14. Você não precisa da sua extensão. Basta fazer: