Swift - detect touched coordinate of UIView - swift

I am trying to make a custom jump bar which can be attached to UITableView.
What I want to achieve now is if user touches A and slides through Z, I want print out A, B, C, D..., Z. Currently, it only prints A, A, A...A. Is there anyway I can achieve it?
Each letter is UIButton subview of UIView.
class TableViewJumpBar{
let tableView: UITableView
let view: UIView
let jumpBar: UIView!
private var jumpIndexes: [Character]
init(tableView: UITableView, view: UIView){
self.view = view
self.tableView = tableView
jumpBar = UIView(frame: .zero)
jumpBar.backgroundColor = UIColor.gray
let aScalars = "A".unicodeScalars
let aCode = aScalars[aScalars.startIndex].value
jumpIndexes = (0..<26).map {
i in Character(UnicodeScalar(aCode + i)!)
}
}
func setFrame(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat){
guard jumpIndexes.count > 0 else{
print("Jump indexes cannot be empty")
return
}
//Remove jumpbar and remove all subviews
jumpBar.removeFromSuperview()
for subView in jumpBar.subviews{
subView.removeFromSuperview()
}
jumpBar.frame = CGRect(x: x, y: y, width: width, height: height)
let height = height/CGFloat(jumpIndexes.count)
for i in 0..<jumpIndexes.count{
let indexButton = UIButton(frame: CGRect(x:CGFloat(0.0), y: CGFloat(i)*height, width: width, height: height))
indexButton.setTitle(String(jumpIndexes[i]), for: .normal)
indexButton.addTarget(self, action: #selector(jumpIndexButtonTouched(_:)), for: .allEvents)
jumpBar.addSubview(indexButton)
}
self.view.addSubview(jumpBar)
}
///Touch has been begun
#objc private func jumpIndexButtonTouched(_ sender: UIButton!){
print(sender.titleLabel?.text)
}

this code works and print start and finish characters.
class ViewController: UIViewController {
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myView: UIView!
var startChar = ""
var finishChar = ""
override func viewDidLoad() {
super.viewDidLoad()
let table = TableViewJumpBar(tableView: myTableView, view: myView)
table.setFrame(x: 0, y: 0, width: 100, height: self.view.frame.size.height)
print(myView.subviews[0].subviews)
setPanGesture()
}
func setPanGesture() {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panRecognized))
self.myView.addGestureRecognizer(pan)
}
#objc func panRecognized(sender: UIPanGestureRecognizer) {
let location = sender.location(in: myView)
if sender.state == .began {
for button in myView.subviews[0].subviews {
if let button = button as? UIButton, button.frame.contains(location), let startCharacter = button.titleLabel?.text {
self.startChar = startCharacter
}
}
} else if sender.state == .ended {
for button in myView.subviews[0].subviews {
if let button = button as? UIButton, button.frame.contains(location), let finishCharacter = button.titleLabel?.text {
self.finishChar = finishCharacter
}
}
print("start with \(startChar), finish with \(finishChar)")
}
}
}
In your code I delete Button action. you can use labels instead Buttons.
class TableViewJumpBar {
let tableView: UITableView
let view: UIView
let jumpBar: UIView!
private var jumpIndexes: [Character]
init(tableView: UITableView, view: UIView){
self.view = view
self.tableView = tableView
jumpBar = UIView(frame: .zero)
jumpBar.backgroundColor = UIColor.gray
let aScalars = "A".unicodeScalars
let aCode = aScalars[aScalars.startIndex].value
jumpIndexes = (0..<26).map {
i in Character(UnicodeScalar(aCode + i)!)
}
}
func setFrame(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat){
guard jumpIndexes.count > 0 else{
print("Jump indexes cannot be empty")
return
}
jumpBar.removeFromSuperview()
for subView in jumpBar.subviews{
subView.removeFromSuperview()
}
jumpBar.frame = CGRect(x: x, y: y, width: width, height: height)
let height = height/CGFloat(jumpIndexes.count)
for i in 0..<jumpIndexes.count{
let indexButton = UIButton(frame: CGRect(x:CGFloat(0.0), y: CGFloat(i)*height, width: width, height: height))
indexButton.setTitle(String(jumpIndexes[i]), for: .normal)
jumpBar.addSubview(indexButton)
}
self.view.addSubview(jumpBar)
}
}
Some output results:

Related

How to fixed vertical positioning glitch in Mapbox callout

So I have a custom AnnotationView class (what the pin itself looks like) and a custom CalloutView class (what the bubble that appears above it looks like) For some reason, I get this odd behavior where if I press the annotation, it selects and appears properly (as seen in the first picture), but then if I deselect the annotation and reselect it (without moving the map at all), the callout reappears in the wrong positioning. The problem appears to be the rect.origin.y value, but I'm not sure--I tried hardcoding it but the problem still persists. In the video, I click my mouse down and it causes the annotation to move up. Any help much appreciated!
class AnnotationView: MGLAnnotationView {
weak var delegate: MapAnnotationDelegate?
var pointAnnotation: MGLPointAnnotation?
required override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
if reuseIdentifier == "default" {
super.centerOffset = CGVector(dx: 0, dy: -17.5)
}
let rect = CGRect(x: 0, y: 0, width: 35, height: 35)
let button = UIButton(frame: rect)
let image = UIImage(named: reuseIdentifier ?? "default")?.resize(targetSize: CGSize(width: 35, height: 35))
button.setImage(image, for: .normal)
button.setImage(image, for: .selected)
button.setImage(image, for: .highlighted)
button.imageView?.contentMode = .scaleAspectFit
button.addTarget(self, action: #selector(annotationAction), for: .touchUpInside)
frame = rect
addSubview(button)
isEnabled = false
}
required init?(coder: NSCoder) {
return nil
}
#objc private func annotationAction() {
delegate?.tappedAnnotation(annotation: pointAnnotation!)
}
}
class CustomCalloutView: UIView, MGLCalloutView {
var representedObject: MGLAnnotation
// Allow the callout to remain open during panning.
let dismissesAutomatically: Bool = false
let isAnchoredToAnnotation: Bool = true
// https://github.com/mapbox/mapbox-gl-native/issues/9228
override var center: CGPoint {
set {
var newCenter = newValue
newCenter.y -= bounds.midY
super.center = newCenter
}
get {
return super.center
}
}
lazy var leftAccessoryView = UIView() /* unused */
lazy var rightAccessoryView = UIView() /* unused */
weak var delegate: MGLCalloutViewDelegate?
let tipHeight: CGFloat = 10.0
let tipWidth: CGFloat = 20.0
let mainBody: UIButton
required init(representedObject: MGLAnnotation) {
self.representedObject = representedObject
self.mainBody = UIButton(type: .system)
super.init(frame: .zero)
backgroundColor = .clear
mainBody.backgroundColor = .white
mainBody.tintColor = .black
mainBody.contentEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
mainBody.layer.cornerRadius = 4.0
addSubview(mainBody)
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - MGLCalloutView API
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
delegate?.calloutViewWillAppear?(self)
view.addSubview(self)
// Prepare title label.
mainBody.setTitle(representedObject.title!, for: .normal)
mainBody.sizeToFit()
mainBody.backgroundColor = .red
if isCalloutTappable() {
// Handle taps and eventually try to send them to the delegate (usually the map view).
mainBody.addTarget(self, action: #selector(CustomCalloutView.calloutTapped), for: .touchUpInside)
} else {
// Disable tapping and highlighting.
mainBody.isUserInteractionEnabled = false
}
// Prepare our frame, adding extra space at the bottom for the tip.
let frameWidth = mainBody.bounds.size.width
let frameHeight = mainBody.bounds.size.height + tipHeight
let frameOriginX = rect.origin.x + (rect.size.width/2.0) - (frameWidth/2.0)
let frameOriginY = rect.origin.y - frameHeight
frame = CGRect(x: frameOriginX, y: frameOriginY, width: frameWidth, height: frameHeight)
if animated {
alpha = 0
UIView.animate(withDuration: 0.2) { \[weak self\] in
guard let strongSelf = self else {
return
}
strongSelf.alpha = 1
strongSelf.delegate?.calloutViewDidAppear?(strongSelf)
}
} else {
delegate?.calloutViewDidAppear?(self)
}
}
func dismissCallout(animated: Bool) {
if (superview != nil) {
if animated {
UIView.animate(withDuration: 0.2, animations: { \[weak self\] in
self?.alpha = 0
}, completion: { \[weak self\] _ in
self?.removeFromSuperview()
})
} else {
removeFromSuperview()
}
}
}
// MARK: - Callout interaction handlers
func isCalloutTappable() -> Bool {
if let delegate = delegate {
if delegate.responds(to: #selector(MGLCalloutViewDelegate.calloutViewShouldHighlight)) {
return delegate.calloutViewShouldHighlight!(self)
}
}
return false
}
#objc func calloutTapped() {
if isCalloutTappable() && delegate!.responds(to: #selector(MGLCalloutViewDelegate.calloutViewTapped)) {
delegate!.calloutViewTapped!(self)
}
}
// MARK: - Custom view styling
override func draw(_ rect: CGRect) {
// Draw the pointed tip at the bottom.
let fillColor: UIColor = .white
let tipLeft = rect.origin.x + (rect.size.width / 2.0) - (tipWidth / 2.0)
let tipBottom = CGPoint(x: rect.origin.x + (rect.size.width / 2.0), y: rect.origin.y + rect.size.height)
let heightWithoutTip = rect.size.height - tipHeight - 1
let currentContext = UIGraphicsGetCurrentContext()!
let tipPath = CGMutablePath()
tipPath.move(to: CGPoint(x: tipLeft, y: heightWithoutTip))
tipPath.addLine(to: CGPoint(x: tipBottom.x, y: tipBottom.y))
tipPath.addLine(to: CGPoint(x: tipLeft + tipWidth, y: heightWithoutTip))
tipPath.closeSubpath()
fillColor.setFill()
currentContext.addPath(tipPath)
currentContext.fillPath()
}
}
For context, the relevant Mapbox map delegate functions are:
func tappedAnnotation(annotation: MGLPointAnnotation) {
let selectedAnnotation = control.mapView.selectedAnnotations.first
guard control.mapView.annotations != nil, selectedAnnotation !== annotation else { return }
//control is the actual map being passed into this coordinator function
for annotation in control.mapView.annotations! {
if annotation.subtitle == "PrimaryAnno" {
control.mapView.removeAnnotation(annotation)
}
}
control.mapView.selectAnnotation(annotation, animated: false, completionHandler: nil)
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
if annotation is MGLPointAnnotation {
var reuseid = ""
//I use the subtitles to indicate which image I use for my annotations
if annotation.subtitle! == "primaryAnno" {
reuseid = "default"
} else {
reuseid = annotation.subtitle!
}
if let reusable = mapView.dequeueReusableAnnotationView(withIdentifier: reuseid) as? AnnotationView {
reusable.pointAnnotation = annotation as? MGLPointAnnotation
return reusable
} else {
let new = AnnotationView(reuseIdentifier: reuseid)
new.delegate = self
new.pointAnnotation = annotation as? MGLPointAnnotation
return new
}
} else {
return nil
}
}
And this is a protocol I use to define the tapping on an annotation:
protocol MapAnnotationDelegate: AnyObject {
func tappedAnnotation(annotation: MGLPointAnnotation)
}
UPDATE: I figured out that the 'correction' happens when I click on the map or change the region, so the issue is with the initial tap on annotation. I've tried setting a breakpoint in the tappedAnnotation function and it seems like once that function ends (it subsequently redirects to the annotationAction() function), the callout just renders in the wrong position.
https://youtu.be/4x5o9T6p8aw

Swift Xib UiView BottomSheet being called multiple times

so i make this bottomsheet view with xib and theres nothing wrong with my code, its just i only want to show it once, i mean like everytime i click the button its get triggered. which is fine but if i rapidly click the button it will also load bunch of time according on how many times i click. i only want to show once i mean no matter how much you rapidly click it only gonna show the xib view once, until i dismiss the button on the xib and it will do the same thing.
here's some video to make it more clearly
https://drive.google.com/file/d/12pwGdTiP_1QZlYc8tV-BlIIQQ5yYrfto/view?usp=sharing
i put a gif on that gdrive link
for the code
Xib Controller :
OrderActionSheetView: UIViewController {
#IBOutlet weak var Text: UILabel!
#IBOutlet weak var vieww: UIView!
#IBOutlet weak var botView: UIView!
#IBAction func cobaLagiBTn(_ sender: Any) {
let closeView = screenSize.height
UIView.animate(withDuration: 0.3, animations: {
self.view.alpha = 0.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
let frame = self.view.frame
self.view.frame = CGRect(x: 0, y: closeView, width: frame.width, height: frame.height)
})
}
let fullView: CGFloat = 0
let screenSize: CGRect = UIScreen.main.bounds
override func viewDidLoad() {
super.viewDidLoad()
vieww.layer.cornerRadius = 20
vieww.layer.borderWidth = 0.5
vieww.layer.borderColor = UIColor(red:222/255, green:225/255, blue:227/255, alpha: 1).cgColor
vieww.clipsToBounds = true
botView.layer.masksToBounds = false
botView.layer.shadowColor = UIColor.black.cgColor
botView.layer.shadowOpacity = 0.14
botView.layer.shadowOffset = CGSize(width: 0, height: 0)
botView.layer.shadowRadius = 2.7
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3) { [weak self] in
let frame = self?.view.frame
let yComponent = self?.fullView
self?.view.frame = CGRect(x: 0, y: yComponent!, width: frame!.width, height: frame!.height)
}
}
func prepareBackgroundView(){
let blurEffect = UIBlurEffect.init(style: .light)
let visualEffect = UIVisualEffectView.init(effect: blurEffect)
let bluredView = UIVisualEffectView.init(effect: blurEffect)
bluredView.contentView.addSubview(visualEffect)
visualEffect.frame = UIScreen.main.bounds
bluredView.frame = UIScreen.main.bounds
view.insertSubview(bluredView, at: 0)
}
func Show(){
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
}
View Controller :
class BottomSheetViewController: UIViewController {
#IBAction func Button(_ sender: Any) {
let text = "Connection Failed"
addBottomSheetView(text: text)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
// 3- Adjust bottomSheet frame and initial position.
let height = view.frame.height
let width = view.frame.width
bottomSheetVC.view.frame = CGRect(x: 0, y: self.view.frame.maxY, width: width, height: height)
bottomSheetVC.Text.text = text
}
}
i just need to know how to stop popping up twice, cause its kinda really some big bugs.....
like i said earlier i just want the xib view to shown only once no matter how many times you rapidly click the button.
Thanks guys :)
As here you add a new instance every click
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
So either
1-You need to add a bool variable like
var isShown = false
and in beginning of method add this code
guard !isShown else { return }
isShown = true
and when you remove the view make
isShown = false
Or
2- create an instance variable like
var bottomSheetVC:OrderActionSheetView?
and in beginning of method
guard bottomSheetVC == nil else { return }
bottomSheetVC = OrderActionSheetView()
and when you remove it do
bottomSheetVC.view.removeFromSuperview()
bottomSheetVC = nil

Use Uitapgesture recognizer on multiple image views that are not declared

My swift code uses func addBox to add and append image views to the uiview controller. All I want to do is when one of the image views are tapped is for func viewClicked to be activated. Right now nothing is happening and nothing is being written into the debug area.
import UIKit
class ViewController: UIViewController {
var ht = -90
var ww = 80
var hw = 80
var arrTextFields = [UIImageView]()
var b7 = UIButton()
override func viewDidLoad() {
[b7].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
$0.backgroundColor = .systemOrange
}
b7.frame = CGRect(x: view.center.x-115, y: view.center.y + 200, width: 70, height: 40)
b7.addTarget(self, action: #selector(addBOx), for: .touchUpInside)
for view in self.arrTextFields {
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewClicked)))
}
}
#objc func viewClicked(_ recognizer: UITapGestureRecognizer) {
print("tap")
}
//func that adds imageview.
#objc func addBOx() {
let subview = UIImageView()
subview.isUserInteractionEnabled = true
arrTextFields.append(subview)
view.addSubview(subview)
subview.frame = CGRect(x: view.bounds.midX - 0, y: view.bounds.midY + CGFloat(ht), width: CGFloat(ww), height: 35)
subview.backgroundColor = .purple
ht += 50
arrTextFields.append(subview)
}
}
You need to enable user interation
for view in self.arrTextFields {
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewClicked)))
}
#objc func viewClicked(_ recognizer: UITapGestureRecognizer) {
print("tap")
}

Segue background flicker upon first load in Swift

I am getting a flicker (likely view controller not fully loaded) for a split second upon first load on my second view controller. I've tried to preset through a segue as well as setting it in a custom initializer prior to load. The affect that I've created is to keep the same background for each view controller. Note, that this only occurs upon first transition over to the view controller. It doesn't happen after the first segue.
Help is greatly appreciated. I'm including the applicable code below.
First View Controller:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate, UIScrollViewDelegate
{
// Background Image
var backgroundImage: UIImageView!
var backgroundImages: [String] = ["canyon.jpeg","channel.jpeg","glacier.jpeg","northernlights.jpeg", "jungle"]
var currentBackground = 0
init(_ coder: NSCoder? = nil)
{
// Logic Goes Here
if let coder = coder
{
super.init(coder: coder)!
} else
{
super.init(nibName: nil, bundle: nil)
}
}
required convenience init(coder: NSCoder)
{
self.init(coder)
}
override func viewDidLoad()
{
setUpView()
super.viewDidLoad()
}
func setUpView()
{
backgroundImage = UIImageView(frame: CGRect( x: 0 , y: 0, width: ScreenWidth, height: ScreenHeight))
backgroundImage.image = UIImage(named: backgroundImages[currentBackground])
backgroundImage.alpha = 1
let playButtonPercent:CGFloat = 0.75
playButton = UIButton(frame: CGRect(x: (ScreenWidth * 0.5) - (ScreenWidth * playButtonPercent / 2), y: (ScreenHeight * (3 / 4)) - (ScreenHeight * (15/480)), width: (ScreenWidth * playButtonPercent), height: ScreenHeight * (30/480)))
playButton.setTitle("PLAY", for: UIControlState())
playButton.addTarget(self, action: #selector(self.playRelease), for: .touchUpInside)
self.view.addSubview(backgroundImage)
self.view.addSubview(playButton)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if (segue.identifier == "Play")
{
if let nextViewController = (segue.destination as? GameListViewController)
{
nextViewController.currentBackground = currentBackground
nextViewController.backgroundImage.image = UIImage(named: self.backgroundImages[currentBackground])
}
}
}
func playRelease()
{
// Segue
performSegue(withIdentifier: "Play", sender: self)
}
}
View Controller 2:
class GameListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
{
let backgroundImages = ["canyon.jpeg","channel.jpeg","glacier.jpeg","northernlights.jpeg", "jungle"]
var currentBackground = 0
var backgroundImage: UIImageView!
override func viewDidLoad()
{
backgroundImage = UIImageView(frame: CGRect( x: 0 , y: 0, width: ScreenWidth, height: ScreenHeight))
super.viewDidLoad()
setUpView()
}
}
func setUpView()
{
backgroundImage.image = UIImage(named: backgroundImages[currentBackground])
backgroundImage.alpha = 1
self.view.addSubview(backgroundImage)
view.sendSubview(toBack: backgroundImage)
}
}

UISwipeGestureRecognizer not recognized when added to UIView's subviews

I currently have a subclass of UIView which contains numerous subviews. I wish to add a UISwipeGesture to the subviews but unfortunately the swipe gesture is not recognized. I've set userInteractionEnabled = true and direction of the swipe gesture but nothing works.
public class CardStackView: UIView{
public var dataSource = [UIImage]()
private var swipeGuesture: UISwipeGestureRecognizer!
override public func layoutSubviews() {
for img in dataSource{
let view = AppView(image: img, frame: self.frame)
self.addSubview(view)
}
animateSubview()
self.userInteractionEnabled = true
}
func animateSubview(){
for (index, sView) in self.subviews.enumerate() {
swipeGuesture = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGuestureDidSwipeRight(_:)))
swipeGuesture.direction = .Right
sView.addGestureRecognizer(swipeGuesture)
sView.userInteractionEnabled = true
let move: CGFloat = CGFloat(-20 + index * 20)
let opacity = Float(1 - 0.2 * CGFloat(index))
sView.shadowOpacity(opacity).shadowOffset(CGSizeMake(20 - CGFloat(index) * 5, 20 - CGFloat(index) * 5)).shadowRadius(5).moveX(-move).moveY(-move).gravity().shadowColor(UIColor.grayColor()).duration(1)
.completion({
}).animate()
}
}
func swipeGuestureDidSwipeRight(gesture: UISwipeGestureRecognizer) {
print("Swiped right")
let subview = self.subviews[0]
subview.moveX(-60).duration(1).animate()
}
}
Example
class ExampleController: UIViewController {
var stackView: CardStackView!
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
self.view.addSubview(stackView)
}
}
self.view.bringSubviewToFront(yourSubview)
try this code for all your subviews and if it doesn't work try this in your controller class for your CardStackView.
Try to call setNeedsLayout for stackView:
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
stackView.setNeedsLayout()
self.view.addSubview(stackView)
}