Swift Variable used within its own initial value while using #selector - ios

I'm trying to invoke my method from selector (action) but getting error Variable used within its own initial value Below is my code snippet
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sendRequest(tapGestureRecognizer, userID)))
Below is the method which I'm calling
#objc func sendRequest(tapGestureRecognizer: UITapGestureRecognizer, identifer: String) {
print("hello world")
}
My method accepts 2 paramters. I don't know how to call it. The way I'm currently calling the sendRequestMethod is throwing error.
Please help me to get it resolved.

when those functions are inside an UIView, as GestureRecognizers usually are, then you can make a hittest and find what UIView was under your touch.
Consider this is not the ideal way to catch touched views. As every UIView also offers you a .tag you can set a numbering and use it to make assumtions to whom the hit UIView fits. See Example
class checkCheckView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sendRequest(recognizer:identifer:)) )
#objc func sendRequest(recognizer:UITapGestureRecognizer, identifer:String) {
print("hello world")
// your problem will be, how is identifier passed in here?
}
let PanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panning(gesture:)))
#objc func panning(gesture:UIPanGestureRecognizer) {
//print("pan-translation %#", gesture.translation(in: self).debugDescription)
let location = gesture.location(ofTouch: 0, in: self)
//print("pan-location %#",location.debugDescription);
let hitview = self.hitTest(location, with: nil)
if hitview != nil {
let UserIDTag = hitview?.tag ?? 0
print("userId by UIView tag",UserIDTag)
}
}
}
Hint: Much easier is to subclass UIView and prepare a property that holds UserID given when allocation is done. addtarget: action:#selector() to each of them. The action/selector method gets invoked and the sender is passed in and because you know the type of the sender (YourUserUIView) you find the UserID.
With GestureRecognizer's you tend to write recognition code that can become very complex the more complex your UI will be, instead of just passing objects to actions that tell you what you are looking for.

Related

BulletinBoard assign gesture for ImageView

I'm using BulletinBoard (BLTNBoard) to create dialogs in my iOS app. There's an option to embed image inside it. I would like to extend it's functionality and allow user to manipulate this image using tap gesture. But eventually when I assign a gesture to it's imageView using addGestureRecognizer nothing happens.
Here's how I initiliaze bulletin and add gesture to the image:
class ViewController: UIViewController {
lazy var bulletinManager: BLTNItemManager = {
let rootItem: BLTNPageItem = BLTNPageItem(title: "")
return BLTNItemManager(rootItem: rootItem)
}()
override func viewDidLoad() {
//etc code
let bulletinManager: BLTNItemManager = {
let item = BLTNPageItem(title: "Welcome")
item.descriptionText = "Pleas welcome to my app"
item.actionButtonTitle = "Go"
item.alternativeButtonTitle = "Try to tap here"
item.requiresCloseButton = false
item.isDismissable = false
item.actionHandler = { item in
self.bulletinManager.dismissBulletin()
}
item.alternativeHandler = { item in
//do nothing by now
}
//
item.image = UIImage(named: "welcome")
//adding gesture to its imageView
item.imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: Selector("tapTap:"))
item.imageView?.addGestureRecognizer(tap)
return BLTNItemManager(rootItem: item)
}()
}
#objc func tapTap(gestureRecognizer: UITapGestureRecognizer) {
print("TAPTAP!!!!!!")
}
}
and nothing happens at all (no message printed in console).
However if I assign action inside alternative button it works as expected:
item.alternativeHandler = { item in
item.imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: Selector("tapTap:"))
item.imageView?.addGestureRecognizer(tap)
}
I guess the only thing which can prevent me to assign the tap event to it properly is that imageView becomes available much later than the bulletin is created (for example only when it is shown on the screen).
Could you please help and correct my code. Thanks
upd.
Ok, based on Philipp's answer I have the following solution:
class myPageItem: BLTNPageItem {
override func makeContentViews(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView] {
let contentViews = super.makeContentViews(with: interfaceBuilder)
let imageView=super.imageView
imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: #selector(tapTap))
imageView?.addGestureRecognizer(tap)
return contentViews
}
#objc func tapTap(gestureRecognizer: UITapGestureRecognizer) {
print("TAPTAP!!!!!!")
}
}
When you're working with an open source library, it's easy to check out the source code to find the answer.
As you can see here, image setter doesn't initiate the image view.
Both makeContentViews makeArrangedSubviews (which are responsible for views initializing) doesn't have any finish notification callbacks.
Usually in such cases I had to fork the repo and add functionality by myself - then I'll make a pull request if I think this functionality may be needed by someone else.
But luckily for you the BLTNPageItem is marked open, so you can just subclass it. Override makeContentViews and add your logic there, something like this:
class YourOwnPageItem: BLTNPageItem {
override func makeContentViews(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView] {
let contentViews = super.makeContentViews(with: interfaceBuilder)
// configure the imageView here
return contentViews
}
}

UICollectionViewListCell and custom cell accessory in iOS 14

My issues with new collection view list cells is that I'm not able to add action handlers to a custom accessory view.
I've been trying to do the following:
protocol TappableStar: class {
func onStarTapped(_ cell: UICollectionViewCell)
}
class TranslationListCell: UICollectionViewListCell {
let starButton: UIButton = {
let starButton = UIButton()
let starImage = UIImage(systemName: "star")!.withRenderingMode(.alwaysTemplate)
starButton.setImage(starImage, for: .normal)
starButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
starButton.addTarget(self, action: #selector(starButtonPressed(_:)), for: .touchUpInside)
starButton.tintColor = .systemOrange
return starButton
}()
var translation: TranslationModel?
weak var link: TappableStar?
override init(frame: CGRect) {
super.init(frame: frame)
accessories = [.customView(configuration: .init(customView: starButton, placement: .trailing(displayed: .always)))]
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc private func starButtonPressed(_ sender: UIButton) {
link?.onStarTapped(self)
}
override func updateConfiguration(using state: UICellConfigurationState) {
// Create new configuration object and update it base on state
var newConfiguration = TranslationContentConfiguration().updated(for: state)
// Update any configuration parameters related to data item
newConfiguration.inputText = translation?.inputText
newConfiguration.outputText = translation?.outputText
contentConfiguration = newConfiguration
}
}
I subclass UICollectionViewListCell, create a button with target-action handler and add it to accessories array. I also have my own implementation of cell configuration.
Now, I create a protocol where I delegate action handling to my view controller (I also implemented new cell registration API and set cell.link = self).
My problem here is that my accessory button doesn't call starButtonPressed although this accessory view is responsive (it changes color when highlighted).
My idea is that there might be something wrong with the way I implement my action handling with a custom accessory but there seems to be little to none information about this new api.
Moreover, when choosing between predefined accessories, some of them have actionHandler closures of type UICellAccessory.ActionHandler but I don't seem to understand how to properly implement that.
Any ideas would be much appreciated.
iOS 14, using UIActions
Since iOS 14 we can initialise UIButton and other UIControls with primary actions. It becomes similar to handlers of native accessories. With this we can use any parametrized method we want. And parametrising is important, because usually we want to do some action with specific cell. #selector's cannot be parametrised, so we can't pass any information to method about which cell is to be updated.
But this solution works only for iOS 14+.
Creating UIAction:
let favoriteAction = UIAction(image: UIImage(systemName: "star"),
handler: { [weak self] _ in
guard let self = self else { return }
self.handleFavoriteAction(for: your_Entity)
})
Creating UIButton:
let favoriteButton = UIButton(primaryAction: favoriteAction)
Creating accessory:
let favoriteAccessory = UICellAccessory.CustomViewConfiguration(
customView: favoriteButton,
placement: .leading(displayed: .whenEditing)
)
Using
cell.accessories = [.customView(configuration: favoriteAccessory)]
I solved my issue by adding tap gesture recognizer to my accessory's custom view. So it works like this:
let customAccessory = UICellAccessory.CustomViewConfiguration(
customView: starButton,
placement: .trailing(displayed: .always))
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(starButtonPressed(_:)))
customAccessory.customView.addGestureRecognizer(tapGesture)
accessories = [.customView(configuration: customAccessory)]
Haven't seen it documented anywhere so hope it helps somebody.
I followed a similar approach to yours, but instead of a UITapGestureRecognizer, I added a target to the button.
var starButton = UIButton(type: .contactAdd)
starButton.addTarget(self, action: #selector(self.starButtonTapped), for: .touchUpInside)
let customAccessory = UICellAccessory.CustomViewConfiguration(customView: starButton, placement: .trailing(displayed: .always))
cell.accessories = [.customView(configuration: customAccessory)]
I first tried the tap gesture recognizer and it didn't work for me.

UIGestureRecognizer created in UIView code not working

I've got a custom subclass of UIStackView that's got some UIImageViews inside it. I want the view to accept a pan gesture to track a slow swipe across its surface. When I configure a UIPanGestureRecognizer in Interface Builder it works, but not in code. Here's an abbreviated version of the class:
#IBDesignable
class CustomView: UIStackView {
private let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(CustomView.singleFingerSwipe))
override init(frame: CGRect) {
super.init(frame: frame)
initializeProperties()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeProperties()
}
#IBAction
private func singleFingerSwipe(_ sender: UIPanGestureRecognizer) {
print("Panning...")
}
private func initializeProperties() {
addGestureRecognizer(panRecognizer)
for _ in 1...3 {
let imageView = UIImageView(image: UIImage(named: "MyImage"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
addArrangedSubview(imageView)
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
imageView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
}
}
}
As I swipe around inside the star view, no output prints. But if I drop a Pan Gesture Recognizer onto the view in IB and hook it up to the same selector, it works fine. What am I missing?
make your panRecognizer lazy var so that its initialization is delayed till its first use and till the time the frame of stack view got set. like
private lazy var panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(CustomView.singleFingerSwipe))
or
move the declaration of panRecognizer to initializeProperties() method right before addGesture call. like
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(CustomView.singleFingerSwipe))
addGestureRecognizer(panRecognizer)
EDIT
The reason is the target is setting null and hence it unable to fire the method, when you are initializing with "private let ..". Since the init gets called later and object isn't initialized for CustomView. Here is the console log when declared with let(constant):
Printing description of self.panRecognizer:
; target= <(action=singleFingerSwipe:, target=<(null) 0x0>)>>
But when its lazy var or declared within the method after init is called, the instance for CustomeView is generated till the time of use of panRecognizer. the console print is:
Printing description of self.panRecognizer.storage.some:
; target= <(action=singleFingerSwipe:, target=)>>
here you can see the target is set.
P.S. you can check the same with debugger

Swift3 Extension on "Any?" -ish class?

Shorter explanation:
You often want to extend on "target" ... and targets are usually Any?. But you can't have an extension on Any. How to do it?
Consider this,
extension UIViewController {
func add(tap v:UIView, _ action:Selector) {
let t = UITapGestureRecognizer(target: self, action: action)
v.addGestureRecognizer(t)
}
}
Excellent, you can now...
self.tap(redButton, #selector(clickedRedButton))
... in any view controller.
But you can do the same thing to just about any target.
So, to use the extension on a UITableViewCell say, you have to also have....
extension UIGestureRecognizerDelegate {
func add(tap v:UIView, _ action:Selector) {
let t = UITapGestureRecognizer(target: self, action: action)
v.addGestureRecognizer(t)
}
}
The target argument of UITapGestureRecognizer is actually Any?
But, you can not do this ...
extension Any {
What's the solution? How to make an extension that will work on the Any?, as for example in the first argument of UITapGestureRecognizer ?
Or as Conner'c comment suggests, is there a way to:
extension UIViewController or UIView {
rather than copying and pasting it twice?
"Any" is adhered to (passively) by every struct/class. An extension to Any would add that functionality to every single type in the language and your code. This isn't currently possible, and I doubt it ever would be (or should be).
Anyway, here are a few ways to solve this problem.
My preference is a protocol extension that adds the functionality:
protocol TapGestureAddable {
func addTapGestureRecognizer(to view: UIView, with action: Selector) -> UITapGestureRecognizer
}
extension TapGestureAddable {
func addTapGestureRecognizer(to view: UIView, with action: Selector) -> UITapGestureRecognizer {
let recognizer = UITapGestureRecognizer(target: self, action: action)
view.addGestureRecognizer(recognizer)
return recognizer
}
}
extension UIViewController: TapGestureAddable { }
extension UIView: TapGestureAddable { }
This forces you to knowingly choose to add the functionality to a given class, (a good thing IMO) without having to duplicate any meaningful code.
Possibly a better option would be to make this logic an extension of UIView instead:
extension UIView {
func addTapGestureRecognizer(with responder: Any, for action: Selector) -> UITapGestureRecognizer {
let recognizer = UITapGestureRecognizer(target: responder, action: action)
self.addGestureRecognizer(recognizer)
return recognizer
}
func addTapGestureRecognizer(with action: Selector) -> UITapGestureRecognizer {
let recognizer = UITapGestureRecognizer(target: self, action: action)
self.addGestureRecognizer(recognizer)
return recognizer
}
}
Otherwise, just make a global function:
func addTapGestureRecognizer(to view: UIView, with responder: Any, for action: Selector) -> UITapGestureRecognizer {
let recognizer = UITapGestureRecognizer(target: responder, action: action)
view.addGestureRecognizer(recognizer)
return recognizer
}
Any isn't a class in the way that NSObject is. It is merely a keyword that indicates to the Swift compiler that a variable/constant/parameter may refer to any object or struct instance, so it isn't possible to extend Any.
If you consider what you are trying to do, you would have a subtle difference between your two extensions anyway;
The UIViewController extension needs to accept a target view (your v) parameter
While, for a UIView extension, you don't need v as this will be self; it doesn't make sense to install a gesture recogniser on some other UIView.
For the UIView extension, you may want to specify a different target for the selector.
You don't add a gesture recogniser to the UIViewController, so it doesn't make, semantically, to extend UIViewController in this way.
So, to me, it seems that the logical extension looks somthing like:
extension UIView {
func add(_ action:Selector,tapHandler target:Any = self) {
let t = UITapGestureRecognizer(target: target, action: action)
self.addGestureRecognizer(t)
}
}
Now, in a UIViewController you can say something like:
self.redButton.add(Selector(("handleTap")), tapHandler: self)
While in a UIView subclass you can say:
self.add(Selector(("handleTap")))

Ios swift replace selector by function

I just want to write one or two lines of code on button click like
print("Button Clicked")
for this i dont want to create a seperate function and call via selector
as
action: #selector(BtnKlkFnc(_:))
I want to simplify like
action: { action in print("Button Clicked")}
I also tried
#selector({print("Button Clicked")})
Can anyone help me to simplify this
Am new to stackoverflow and do not have enough reputations yet, So kindly vote for my question up, so i can vote for your ans
Short answer: You can't do that. Button actions are part of the target/action mechanism built into Cocoa/Cocoa touch. It's based on selectors, and you must create a named method and use it's selector. You can't use a Swift closure as a button action.
EDIT:
Note that it is possible to create a custom subclass of UIButton that has a closure property and invokes that closure when the button is tapped. What you'd do is to make the button's init method set itself up as the target of a touchUpInside event and invoke a method of the button that in turn invokes your closure (after making sure the closure property isn't nil.)
EDIT #2:
Note that it is pretty straightforward to create a custom subclass of UIButton that sets itself up as the target for button presses and keeps a closure.
Here is a sample implementation:
class ClosureButton: UIButton {
var buttonClosure: ((UIButton) -> Void)?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
}
#objc func handleTap(_ sender: UIButton) {
if let buttonClosure = buttonClosure {
buttonClosure(sender)
} else {
print("No button closure defined")
return
}
}
}
And in your view controller:
override func viewDidLoad() {
super.viewDidLoad()
button.buttonClosure = { _ in
print("You tapped the button")
}
}

Resources