addTarget and addGestureRecognizer not working, no crash/error - ios

I a have an overlay with a table in and I'd like to add a Tap gesture recogniser to the background to dismiss the view and also addTarget to a button within the overlay which does the same thing.
The overlay displays fine as expected, however whenever I tap the black background or the cancel button, nothing happens. I've searched for an answer here but nothing found has worked. My code is as follows, followed by a screenshot of the overlay:
class importedFileView: NSObject {
let blackView = UIView()
let importedFileContainerView: UIView = {
let importedFileContainerView = UIView(frame: .zero)
importedFileContainerView.backgroundColor = .white
importedFileContainerView.layer.cornerRadius = 10
importedFileContainerView.layer.masksToBounds = true
return importedFileContainerView
}()
let headerLabel: UILabel = {
let headerLabel = UILabel()
headerLabel.translatesAutoresizingMaskIntoConstraints = false
headerLabel.font = UIFont(name: "HelveticaNeue-Thin" , size: 24)
headerLabel.text = "Attach file"
headerLabel.textColor = .darkGray
headerLabel.adjustsFontSizeToFitWidth = true
return headerLabel
}()
let fileTableView: UITableView = {
let fileTableView = UITableView()
return fileTableView
}()
let updateDetailsButton: UIButton = {
let updateDetailsButton = UIButton()
updateDetailsButton.translatesAutoresizingMaskIntoConstraints = false
updateDetailsButton.backgroundColor = UIColor(r:40, g:86, b:131)
updateDetailsButton.setTitleColor(UIColor.white, for: .normal)
updateDetailsButton.setTitle("Attach selected files", for: .normal)
updateDetailsButton.titleLabel!.font = UIFont(name: "HelveticaNeue-Light" , size: 18)
updateDetailsButton.layer.cornerRadius = 2
return updateDetailsButton
}()
let cancelButton: UIButton = {
let cancelButton = UIButton()
cancelButton.translatesAutoresizingMaskIntoConstraints = false
cancelButton.backgroundColor = UIColor.white
cancelButton.setTitleColor(UIColor(r:40, g:86, b:131), for: .normal)
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.titleLabel!.font = UIFont(name: "HelveticaNeue-Light" , size: 18)
cancelButton.layer.cornerRadius = 2
return cancelButton
}()
let frameHeight: CGFloat = 450
func showFormView(){
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
window.addSubview(blackView)
window.addSubview(importedFileContainerView)
importedFileContainerView.addSubview(headerLabel)
importedFileContainerView.addSubview(fileTableView)
importedFileContainerView.addSubview(updateDetailsButton)
importedFileContainerView.addSubview(cancelButton)
cancelButton.addTarget(self, action: #selector(handleDismiss), for: .touchUpInside)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
layoutViews()
fileTableView.frame = CGRect(x: 30, y: window.frame.height, width: window.frame.width - 60, height: 230)
let frameY = (window.frame.height - frameHeight) / 2
importedFileContainerView.frame = CGRect(x: 20, y: window.frame.height, width: window.frame.width - 40, height: self.frameHeight)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.importedFileContainerView.frame = CGRect(x: 20, y: frameY, width: window.frame.width - 40, height: self.frameHeight)
}, completion: nil
)
}
}
func layoutViews(){
let views = ["v0" : headerLabel, "v1": fileTableView, "v2": updateDetailsButton, "v3": cancelButton]
let leftSpace = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v0]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace1 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v1]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace2 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v2]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace3 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v3]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let topSpacing = NSLayoutConstraint.constraints(withVisualFormat: "V:|-20.0-[v0(40)]-20.0-[v1(230)]-20.0-[v2(50)]-10.0-[v3(50)]-10.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
importedFileContainerView.addConstraints(topSpacing)
importedFileContainerView.addConstraints(leftSpace)
importedFileContainerView.addConstraints(leftSpace1)
importedFileContainerView.addConstraints(leftSpace2)
importedFileContainerView.addConstraints(leftSpace3)
}
func handleDismiss() {
UIView.animate(withDuration: 0.5,
delay: 0.0,
options: .curveEaseInOut,
animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.importedFileContainerView.frame = CGRect(x: 20, y: window.frame.height, width: window.frame.width - 40, height: self.frameHeight)
}
},
completion: { [weak self] finished in
self?.blackView.removeFromSuperview()
self?.importedFileContainerView.removeFromSuperview()
})
}
override init() {
super.init()
}
}

self.blackView.isUserInteractionEnabled = true;
is all you need to add to the blackView (UIView).
Without that, the view doesn't have any interactions enabled and so the gesture recognizer's target/action isn't triggered.
Events are ignored.
https://developer.apple.com/reference/uikit/uiview/1622577-isuserinteractionenabled
You might also want to disable it during animations.

Are you keeping a strong reference to the instance of your importedFileView while your overlays are visible? As far as I tested, all actions are silently ignored when the target is lost.
For example, this does not work:
#IBAction func someAction(_ sender: Any) {
let ifv = importedFileView()
ifv.showFormView()
//`ifv` is released at the end of this method, then your overlays are shown...
}
This works:
let ifv = importedFileView() //keep the instance as a property of your ViewController.
#IBAction func someAction(_ sender: Any) {
ifv.showFormView()
}
Programmatically generated UIViews isUserInteractionEnabled is default to true. You have no need to explicitly set it to true.
By the way, you'd better not name your non-UIView class as ...View, and better make your type names start with capital letter, which makes your code more readable to experienced Swift programmers.

Related

If Button clicked - do animation to image Swift Playgrounds

My goal is simple. When a button is clicked, the button should fade out and so should another image on the screen. I am using Swift Playgrounds. I have already got the button to fade out when clicked working. The main problem is getting the image on the screen to fade out once the button is clicked.
import PlaygroundSupport
import AVFoundation
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let title = UIImage(named: "Picture1")
let imageView = UIImageView(image: title) //this is the image I want to fade out when the button is clicked
imageView.image = title
imageView.frame.size.width = 570
imageView.frame.size.height = 140
imageView.frame.origin.x = 100
imageView.frame.origin.y = 0
imageView.alpha = 0
let button = UIButton(type: UIButton.ButtonType.custom) as UIButton
let image = UIImage(named: "startbutton4.png") as UIImage?
button.frame = CGRect(x: 265, y: 300, width: 249, height: 85)
button.setImage(image, for: [])
button.contentMode = .center
button.imageView?.contentMode = .scaleAspectFit
button.imageView?.contentMode = .scaleAspectFit
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
view.addSubview(imageView)
}
#objc func buttonAction(sender: UIButton) {
sender.isHighlighted = false
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
sender.alpha = 0
}, completion: nil) // <--- this right here is the button fading out
}
}
let master = MyViewController()
PlaygroundPage.current.liveView = master
If anyone has any idea how to do this, please let me know.
What i understand is you want to hide button ... once button is hidden you want to show image .. here is the code for that.. i hope it will help
func buttonAction(sender: UIButton) {
sender.isHighlighted = false
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
sender.alpha = 0
}, completion: { success in
if success {
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
imageView.alpha = 1
})
}
})
}

Custom Button Tap not working in UIView In Table Footer View

I have the most peculiar scenario that I can't seem to fix. I have a custom button that I add as a subview to a UIView. I then add the UIView to the tableFooterView of a table view and I'm not able to have the button tap be detected. Here is the code:
public func configureMyButton() {
let button = CustomButton("My title")
button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
let buttonContainer = UIView()
buttonContainer.addSubview(button)
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-16-[button]", options: [], metrics: [:], views: ["button":button]))
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[button]-8-|", options: [], metrics: [:], views: ["button":button]))
self.tableView.tableFooterView = buttonContainer
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
Now if I change this:
self.view.addSubview(buttonContainer)
The button tap works. This leads me to believe that theres something about tableFooterView that stops the tap from working but I'm not entirely sure what it could be. Any ideas?
The reason why the button wasn't responding to taps was because the buttonContainers frame was completely wrong. So despite everything looking find on screen, the frame was practically none existent and hence the button wasn't responding
public func configureMyButton() {
let button = CustomButton("My title")
button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
let buttonContainer = UIView()
buttonContainer.addSubview(button)
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-16-[button]", options: [], metrics: [:], views: ["button":button]))
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[button]-8-|", options: [], metrics: [:], views: ["button":button]))
buttonContainer.layoutIfNeeded()
buttonContainer.frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.height, height: button.frame.size.height + 16)
self.tableView.tableFooterView = buttonContainer
}
I'm not overly happy with the solution. I'm of the opinion I shouldn't have to have fiddled with the buttonContainer frame. Autolayout should have deduced it's frame to at the very least be the size of its subviews.
As you noted, the button could not be tapped because it was displayed outside the button container frame.
UITableView handles the layout for its header and footer views, so using auto-layout with them takes an additional step.
Don't add your footer view in viewDidLoad(). Instead, override viewDidLayoutSubviews() like this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// manipulating the tableFooterView will trigger viewDidLayoutSubviews()
// so only call this if we haven't added the footer view yet
if tableView.tableFooterView == nil {
configureMyButton()
tableView.layoutTableFooterView()
}
}
Change your configureMyButton() func as shown here:
public func configureMyButton() {
// I don't have your CustomButton() func...
//let button = CustomButton("My title")
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("My Title", for: .normal)
button.backgroundColor = .blue
button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
let buttonContainer = UIView()
// set background to red so we can see it - remove after testing
buttonContainer.backgroundColor = .red
buttonContainer.addSubview(button)
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-16-[button]|", options: [], metrics: [:], views: ["button":button]))
buttonContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[button]-8-|", options: [], metrics: [:], views: ["button":button]))
self.tableView.tableFooterView = buttonContainer
}
And then add this extension:
extension UITableView {
func layoutTableHeaderView() {
guard let tempView = self.tableHeaderView else { return }
tempView.translatesAutoresizingMaskIntoConstraints = false
let width = tempView.bounds.size.width;
let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[tempView(width)]", options: NSLayoutConstraint.FormatOptions(rawValue: UInt(0)), metrics: ["width": width], views: ["tempView": tempView])
tempView.addConstraints(temporaryWidthConstraints)
tempView.setNeedsLayout()
tempView.layoutIfNeeded()
let tempSize = tempView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = tempSize.height
var frame = tempView.frame
frame.size.height = height
tempView.frame = frame
self.tableHeaderView = tempView
tempView.removeConstraints(temporaryWidthConstraints)
tempView.translatesAutoresizingMaskIntoConstraints = true
}
func layoutTableFooterView() {
guard let tempView = self.tableFooterView else { return }
tempView.translatesAutoresizingMaskIntoConstraints = false
let width = tempView.bounds.size.width;
let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[tempView(width)]", options: NSLayoutConstraint.FormatOptions(rawValue: UInt(0)), metrics: ["width": width], views: ["tempView": tempView])
tempView.addConstraints(temporaryWidthConstraints)
tempView.setNeedsLayout()
tempView.layoutIfNeeded()
let tempSize = tempView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = tempSize.height
var frame = tempView.frame
frame.size.height = height
tempView.frame = frame
self.tableFooterView = tempView
tempView.removeConstraints(temporaryWidthConstraints)
tempView.translatesAutoresizingMaskIntoConstraints = true
}
}
Now your footer view will size correctly based on your auto-layout constraints -- so if you add elements to the footer view you won't have to explicitly change your height value.

on single tap UIBarButton display toast and on double tap back action need to perform

I have an UIBarButton in navigation bar, while clicking on back button (first tap) i need to display toast (like warning), on double tap i need to exit from the page in swift,
Following codes used for displaying toast, its working fine,
let toastLabel = UILabel(frame: CGRect(x: 20, y: self.view.frame.size.height-100, width: 350, height: 35))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
toastLabel.textColor = UIColor.white
toastLabel.textAlignment = .center;
toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
toastLabel.text = "demo"
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
self.view.addSubview(toastLabel)
UIView.animate(withDuration: 2.0, delay: 0.1, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
please guide me to acheive this task.
Add an target-action on UIButton for the control event UIControlEventTouchDownRepeat, and do action only when the touch's tapCount is 2. Like below in Swift 3
button.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControlEvents.touchDownRepeat)
And Then add selector as below
func multipleTap(_ sender: UIButton, event: UIEvent) {
let touch: UITouch = event.allTouches!.first!
if (touch.tapCount == 2) {
// do action.
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let button = UIButton(type: .system)
button.frame = CGRect(x: 0, y: 0, width: 80, height: 40)
button.setTitle("Back", for: .normal)
//Gesture Recognizer
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
singleTap.numberOfTapsRequired = 1
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
doubleTap.numberOfTapsRequired = 2
button.addGestureRecognizer(singleTap)
button.addGestureRecognizer(doubleTap)
singleTap.require(toFail: doubleTap)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = barButton
}
#objc func handleSingleTap(sender: UITapGestureRecognizer? = nil) {
let toastLabel = UILabel(frame: CGRect(x: 20, y: self.view.frame.size.height-100, width: 350, height: 35))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
toastLabel.textColor = UIColor.white
toastLabel.textAlignment = .center;
toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
toastLabel.text = "demo"
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
self.view.addSubview(toastLabel)
UIView.animate(withDuration: 2.0, delay: 0.1, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
print("Single Tap detected")
}
#objc func handleDoubleTap(sender: UITapGestureRecognizer? = nil) {
print("Double Tap detected")
}
Use UIControlEventTouchDownRepeat event, and do action when tapCount is 2.

IOS 11 only: Navigation bar label off on top

IOS 11 is causing the main label to move a little bit from the top rather than keeping to the top. The problem only occurs on IOS 11. With different IOS everything looks ok.
Code sample with a setting header:
private func setHeader(agentName: String = "", isTyping: Bool = false) -> Void {
if (agentName.isEmpty) {
self.containerViewController?.navigationItem.titleView = nil
} else {
let headerView: UIView = {
let rect = CGRect(x: 0, y: 0, width: 320, height: 44)
let uiview = UIView(frame: rect)
return uiview
}()
let headerLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: headerView.frame.width, height: 20))
label.font = UIFont.systemFont(ofSize: 18)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let subheaderLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: headerView.frame.width, height: 12))
label.font = UIFont.systemFont(ofSize: 12)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = self.title
return label
}()
headerView.addSubview(headerLabel)
headerView.addSubview(subheaderLabel)
let viewsDictionary = ["header": headerLabel, "subheader": subheaderLabel]
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[header]|", options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[subheader]|", options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[header(20)]-[subheader]", options: [], metrics: nil, views: viewsDictionary))
self.containerViewController?.navigationItem.titleView = headerView
}
}
Beginning with iOS 11, views add to toolbars are now laid out using auto layout. You should add sizing constraints on your headerView. For example:
headerView.widthAnchor.constraintEqualToConstant(320.0).isActive = true
headerView.heightAnchor.constraintEqualToConstant(44.0).isActive = true
Otherwise, auto layout will use the intrinsic content size of your header view which is likely not what you expect.
For more information see the WWDC 2017 session Updating your app for iOS 11.
#beyowulf said that you have to add sizing constraints. I think in your case adding height constraint is okay :
headerView.heightAnchor.constraint(equalToConstant: 22.0).isActive = true

UILabel vertical constraints with NSLayoutConstraints (swift 3 xcode)

Just started using NSLayoutConstraints and for some reason I can't get the vertical constraints to work. Whenever I run my application the label sits centered vertically in the view, regardless of what value I give it.
Here is the current code:
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.bounds)
edgesForExtendedLayout = []
setupViews()
}
let headerTitleLabel: UILabel = {
let label = UILabel()
label.text = "Projects"
label.font = UIFont.boldSystemFont(ofSize: 14)
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
return label
}()
func setupViews() {
let v1 = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 200))
v1.backgroundColor = UIColor.red
self.view.addSubview(v1)
let v2 = UITableView(frame: CGRect(x: 0, y: v1.frame.height, width: view.bounds.width, height: (view.bounds.height-v1.frame.height)))
v2.backgroundColor = UIColor.blue
self.view.addSubview(v2)
v1.addSubview(headerTitleLabel)
v1.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": headerTitleLabel]))
v1.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": headerTitleLabel]))
}

Resources