So I was making a to-do list app, and I had this variable inside VIEWDIDLOAD:
var edit = UIBarButtonItem (title: "Edit", style: .plain, target: self, action: #selector(editfunc))
I wanted that when someone finishes editing, the button changes from 'edit' to 'done' so I wrote this:
#objc func editfunc () {
table.isEditing = !table.isEditing
if table.isEditing == true {
edit.title = "Done"
}
else {
edit.title = "Edit"
}
}
It didn't work. I googled it and it seems like the solution was to write 'var edit' outside of the viewdidload func, and also to use 'lazy' before it. It worked, but I still didn't understand why? What's the difference between defining properities inside and outside viewdidload and why did it work only when I used 'lazy'm, a
It's as Lorem ipsum commented that it's about the scope of the variable.
But to answer your second question about the lazy property is used on variables where you want to use self as Lazy waits for init to be run. self is still available as a property, but as init has not been run yet, you are basically asserting nil to the target property
Related
I'm studying Swift and I have a question.
When we make some button's Action function, sometimes tags are used.
After all to set button's tags with code, we must connect button's Outlet from storyboard.
Why use tags instead of outlet's variable name?
Any number of buttons can have a single action and we then need tag to distinguish action based on button tag. you don't actually need outlet for each button if you are setting tag from storyboard, Here is a detailed articles about tags:
Working With Multiple UIButtons and Utilizing Their Tag Property
Many cases many button have the same ibaction. In this situation , tag can help
As others have said the function could be completely independent of the button, it could be in another class altogether.
Buttons may not be in StoryBoards and could be created programatically, potentially dynamically, so there maybe no case of checking if the button passed to the function is the same as a local variable (as a button may not be an ivar on the class the function is based on)
Tags give an easy way to provide some identifier to button or view that can be cross referenced when the function is called in order to achieve the desired out come
Here's an example to illustrate, bear in mind this is somewhat contrived in order to provide an detail
In you ViewController you need to dynamically create a button but don't want to store it as a variable on the VC. You also have a separate class which is handling taps
We could create an enum to handle button types, this handles the tag and title for the button type
enum ButtonType: String {
case nonIvar
case other
var tag: Int {
switch self {
case .nonIvar:
return 0
case .other:
return 1
}
}
var title: String {
switch self {
case .nonIvar:
return "Non iVar Button"
case .other:
return "Some other Button"
}
}
}
we then have our class that handles the tap function
class ButtonHandler {
#IBAction func handleTap(_ sender: UIButton) {
if sender.tag == ButtonType.nonIvar.tag {
// do something with the none ivar buton
} else {
// handle other type
}
}
}
then in our view controller we create our button hooking the two instances above up
let handler = ButtonHandler()
func addMyButton() {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let buttonType: ButtonType = .nonIvar
myButton.setTitle(buttonType.title, for: .normal)
myButton.tag = buttonType.tag
myButton.addTarget(self, action: #selector(handler.handleTap(_:)), for: .touchUpInside)
view.addSubview(myButton)
}
This means that the handler can be used on any class and the button types could be equally used throughout the app and the behaviour would be the same without duplicating code
Hope this helps!
I wanted to use a custom UIMenuController in WKWebView.
First, I wanted to get rid of the default menu (Copy, Look up, Share), but for some reason I don't know, but it hasn't disappeared.
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(highlightHandler):
return true
default:
return false
}
}
func enableCustomMenu() {
let memo = UIMenuItem(title: "메모", action: #selector(highlightHandler))
UIMenuController.shared.menuItems = [memo]
UIMenuController.shared.update()
}
#objc func highlightHandler(sender: UIMenuItem) { }
I tried using the code above to remove the default menuItems and add custom menuItems called "메모", but it didn't.
How can I show only the items I want called "메모"?
canPerformAction() cannot reject an option in most cases. It can only tell the system that the class it's being called in is willing to provide the needed function. Returning False just says "I can't do that one", and then the next item in the responder chain is called and eventually something is found that says "Yes, I can do that". Having said that, it seems that I get a different result if I override this function on the item that is the first responder. In that case, False actually seems to disable the command. So if you can implement canPerformAction() on the first-responder, do that. If not...
Basically you have to temporarily break the responder chain. You do that by overriding the UIResponder "next" variable so that it conditionally returns nil when you want the chain broken. You don't want it to leave it broken for long or bad things will happen. Anything that was approved by the FirstResponder or things in the responder chain between First and you will still be approved, but that will stop approval of things after you in the chain.
When i pass a static/class method in selector param of UIAccessibilityCustomAction, it is not getting triggered.
The same method works for gesture recognizers/add targets functions
The custom action is set and announced properly. So there's no problem in set up of that. But when i double tap staticTest is not triggered.
If i pass instance method to it, it works.
Code set-up which is not working:
// does not work
newView.accessibilityCustomActions?.append(
UIAccessibilityCustomAction(
name: "staticTest test action",
target: ViewController.self,
selector: #selector(ViewController.staticTest)))
Code Sample:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView(frame: CGRect(x: 20, y: 20, width: 300, height: 300))
newView.backgroundColor = UIColor.red
view.isUserInteractionEnabled = true
view.addSubview(newView)
newView.isAccessibilityElement = true
// works
newView.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(instanceTest)))
// works
newView.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "instanceTest test action", target: self, selector: #selector(instanceTest))
]
// works
newView.addGestureRecognizer(
UITapGestureRecognizer(
target: ViewController.self,
action: #selector(ViewController.staticTest)))
// does not work
newView.accessibilityCustomActions?.append(
UIAccessibilityCustomAction(
name: "staticTest test action",
target: ViewController.self,
selector: #selector(ViewController.staticTest)))
}
#objc static func staticTest() -> Bool {
print("staticTest")
return true
}
#objc func instanceTest() -> Bool {
print("InstanceTest")
return true
}
}
I tried your code and it was difficult to believe the observation. Then I checked the apple docs and here is my analysis.
Even though apple documentation is silent on the use of static/class function as selector. It's kind of indirectly mentioned in the wordings.
https://developer.apple.com/documentation/uikit/uiaccessibilitycustomaction/1620499-init
For UIAccessibilityCustomAction:
target:The object that performs the action.
selector:The selector of target to call when you want to perform the action.
In your code, target is the ViewController object but it's difficult to say that selector belongs to this object as it's a static/class function. Actually, swift doesn't even allow to call static function using an object.
Having said that, it's debatable why static function works with gestureRecogniser.
https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624211-init
The defintion of target is slightly different here.
target: An object that is the recipient of action messages sent by the receiver when it recognizes a gesture.
action: A selector that identifies the method implemented by the target to handle the gesture recognized by the receiver.
So, your code matches the target definiton completely as self is the object which should receive the gesture. But action definition is not completely matched as the target(which is an object and not a class) actually does not implement the static function.
As per my understanding, static function needs to be avoided as selector based on the documentation.
I set up a right navigation button like so inviewWillAppear in . a class ChatMessageViewController..
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(blockPressed(sender:)))
button2.title = "Block"
self.navigationItem.rightBarButtonItem = button2
Now on the click of blockPressed another shared function is called like so...
#objc fileprivate func blockPressed(sender: UIButton) {
XMPPConfig.shared.blockUser(userJID: theUserJID!) //XMPPConfig is another class having some common functions and delegate methods.
}
(This function basically blocks a certain user like blocking a whatsapp user. Once blocking happens, certain delegate methods are called. One such delegate method after which I change the Block button is given as follows..)
func xmppBlocking(_ sender: XMPPBlocking!, didBlockJID xmppJID: XMPPJID!) {
print("successfully blocked!")
ChatMessageViewController.shared.setupUnBlock()
}
Doing this also properly calls setupUnBlock() function in ChatMessageViewController like so...
func setupUnBlock() {
if XMPPConfig.shared.sectionGroupsFlag == false {
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(unblockPressed(sender:)))
button2.title = "Unblock"
self.navigationItem.rightBarButtonItem = button2
}
}
But the button title still remains unchanged...i.e. it is still "Block"..what could be the reason for this...?
You should initialize your UINavigationBarButton using another initializer:
let right = UIBarButtonItem(title: "Some title", style: .plain, target: self, action: #selector(rightNavBarButtonPressed))
You are using UIBarButtonItem(image:... instead.
I tried to duplicate your code and apparently everything works.
So, the problem may be due to three possible problems, as far as i can see.
it may be thread problem. This is unlikley unless you have not turned on the thread checker in xcode.. But inside both unblock and block codes, enter
print(Thread.current)
to see both blocks of codes in setting the rightbarButton is in main thread. If not, you should know how to solve them.
It may be view update problem, unlikely still, but worth a try.. So in your block of codes where you are adding rightBarButton, add one more line of code.
self.navigationController?.viewIfLoaded?.setNeedsLayout()
to update the navigationController's view after you have set the rightBarButtonItem.
The most likely problem in my opinion is that, you have been doubling calling block barButtonItem codes. This means when you have called unblock barButtonItem setup codes, you accidentally called the block barButtonItem too.
To debug this is easy, you just put a statement in each of block of the codes, to see if they appear together.
I'm building a custom dialog for an app in which i would want to use closures that are called when the user taps on something, kinda like this:
var modal = ModalDialog(title: "modal title", buttonClick: { () -> Void in
println("clicked")
})
modal.show()
So I made a class called ModalDialog in which I have an UITapGestureRecognizer like so:
var modalTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("buttonClickAction:"))
modalView.addGestureRecognizer(modalTap)
My problem is that due to the ARC, when the tap is actually recognized and it tries to call buttonClickAction: the class is already deinitialised and the app crashes.
How would i go about keeping the ARC from deinitialising the modal until i explicitly tells it do to so?
Keep a strong reference of your modal dialog in view controller.
var dialog : ModalDialog
func show {
// Keep strong reference dialog by property of your view controller
self.dialog = ModalDialog(title: "modal title", buttonClick: { () -> Void in
println("clicked")
})
self.dialog.show()
}