iOS charts - draw value only when highlighted - ios

I want to highlight and show value only when tapping on the iOS-Chart. I enabled the highlight but not the values because I only want them when tap and highlight
lineChartDataSet.drawValuesEnabled = false
lineChartDataSet.highlightEnabled = true
Do I need this function?
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {}

It's an old question, but I think it's still actual for some developers.
If you want to show values, baloon or highlight bar only while user is touching a chart view you may catch a touch event with UILongPressGestureRecognizer.
I instantiated new TappableLineChartView class from LineChartView. But you can work with BarChartView in the same way. Also if you don't want to instantiate new classes, you can incorporate addTapRecognizer and chartTapped functions in your view controller.
In my example, I show and hide values, but in the same manner, you can show and hide a balloon or another marker.
class TappableLineChartView: LineChartView {
public override init(frame: CGRect)
{
super.init(frame: frame)
addTapRecognizer()
}
public required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
addTapRecognizer()
}
func addTapRecognizer() {
let tapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(chartTapped))
tapRecognizer.minimumPressDuration = 0.1
self.addGestureRecognizer(tapRecognizer)
}
#objc func chartTapped(_ sender: UITapGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
// show
let position = sender.location(in: self)
let highlight = self.getHighlightByTouchPoint(position)
let dataSet = self.getDataSetByTouchPoint(point: position)
dataSet?.drawValuesEnabled = true
highlightValue(highlight)
} else {
// hide
data?.dataSets.forEach{ $0.drawValuesEnabled = false }
highlightValue(nil)
}
}
}

Related

Swift - How to add tap gesture to array of UIViews?

Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}
You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}
You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}

How to make link, phone number clickable (Same behaviour as in textview) in a custom view where i draw text?

I made a view and draw text on it and i want that if any text contains link(Hyperlink) or Phone Number It would be clickable (Same Behaviour As in Text View) So how to Achieve it ?
Code For View In which i am Drawing Text :-
class DrawRectCellView: UIView {
var text: NSAttributedString?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Only override drawRect: if you perform custom drawing.
override func draw(_ rect: CGRect)
{
UIColor.white.setFill()
UIGraphicsGetCurrentContext()?.fill(rect)
// Drawing code
if let attributedText = text {
attributedText.draw(in: rect)
}
}
}
Code For TableCell :-
class DrawRectCell: UITableViewCell {
var cellView: DrawRectCellView?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Initialization code
cellView = DrawRectCellView(frame: self.frame)
if let cell = cellView {
cell.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
cell.contentMode = UIViewContentMode.redraw
}
self.contentView.addSubview(cellView!)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTextString(_ text: NSAttributedString) {
if let view = cellView {
view.text = text
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
i am Setting Text like = www.google.com or any phone number its showing as normal text only (Not Showing Like In textview (it makes it clickable))
First you need to detect your text contain url or numbers like this way.
let input = "This is a test with the URL https://www.hackingwithswift.com to be detected."
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let url = input[range]
print(url)
}
if you detect url after setting to the textview you need to add UITapGestureRecognizer on UITextView like this way.
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap")
textview.addGestureRecognizer(tapGesture)
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .began {
//write your code here for url tap
}
}
You can add UITapGestureRecognizer on DrawRectCellView and set the IndexPath as tag. In the selector method of UITapGestureRecognizer you can get the subviews info of the tapped contentView containing text.
To highlight certain text within the TextView:
Let's say you have your textView textView, then you can use this code to highlight URLs, phone numbers, etc.:
textView.dataDetectorTypes = [] // doesn't work if textfield isEditable is set to true
textView.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.blue] // the default is blue anyway
If you want to only include some specific data types to be detected, add them to the array like so:
textView.dataDetectorTypes = [.link]
Here's a list of types:
https://developer.apple.com/documentation/uikit/uidatadetectortypes
To make an entire view tappable:
You can add a UITapGestureRecognizer to the view (or not if it does not contain a phone number or hyperlink) like so:
https://stackoverflow.com/a/28675664/7270113
This answer may not be using the Swift version you are using. If so the compiler will tell you how to change it, so it will work.
If you don't like using Selectors, I recommend using the library Closures
https://github.com/vhesener/Closures, specifically with this https://vhesener.github.io/Closures/Extensions/UITapGestureRecognizer.html

Why Is My IBDesignable UIButton Subclass Not Rendering In Interface Builder?

In the screen shot below please note that the DropDownButton (the selected view) is not being live rendered. Also, please note the "Designables Up To Date" in the Identity Inspector. Finally, please note the two break points in the assistant editor: if I execute Editor -> Debug Selected Views then both of these break points are hit.
Here's what It looks like when I run it:
Here's the code:
#IBDesignable
class DropDownButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
if image(for: .normal) == nil {
//setImage(#imageLiteral(resourceName: "DropDown"), for: .normal)
let bundle = Bundle(for: DropDownButton.self)
if let image = UIImage(named: "DropDown", in: bundle, compatibleWith: nil) {
setImage(image, for: .normal) // Editor -> Debug Selected Views reaches this statement
}
}
if title(for: .normal) == nil {
setTitle("DropDown", for: .normal) // Editor -> Debug Selected Views reaches this statement
}
addTarget(self, action: #selector(toggle), for: .touchUpInside)
}
override func layoutSubviews() {
super.layoutSubviews()
if var imageFrame = imageView?.frame, var labelFrame = titleLabel?.frame {
labelFrame.origin.x = contentEdgeInsets.left
imageFrame.origin.x = labelFrame.origin.x + labelFrame.width + 2
imageView?.frame = imageFrame
titleLabel?.frame = labelFrame
}
}
override func setTitle(_ title: String?, for state: UIControlState) {
super.setTitle(title, for: state)
sizeToFit()
}
public var collapsed: Bool {
return imageView?.transform == CGAffineTransform.identity
}
public var expanded: Bool {
return !collapsed
}
private func collapse() {
imageView?.transform = CGAffineTransform.identity
}
private func expand() {
imageView?.transform = CGAffineTransform(rotationAngle: .pi)
}
#objc private func toggle(_: UIButton) {
if collapsed { expand() } else { collapse() }
}
}
First Edit:
I added the prepareForInterfaceBuilder method as per #DonMag's answer. Doing so made an improvement but there is still something wrong: Interface builder seems confused about the frame. When I select the button only the title is selected, not the image (i.e. triangle). I added a border; it goes around both the title and the image. Here is a picture:
If I drag the button to a new position then everything moves, title and image.
Also, it surprised me that prepareForInterfaceBuilder made a difference. My understanding of this method it that it allows me to do interface builder only setup such as providing dummy data.
Add this:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
initialize()
}

Custom button view: detect touch down/up, quickly, and fall back dragging to UIScrollView

Sorry if this was already asked/answered.
I'm trying to create a CustomButton (UIView) that detects touch down and up (to customize changes in background color, text color, etc).
I've tried touchesBegan but it reacts too slow.
I've tried UILongPressGestureRecognizer but it doesn't fall back to dragging a UIScrollView below. I mean: when dragging over the button it doesn't drag the scroll view.
Here's my solution so far, using UILongPressGestureRecognizer, with ideas taken from here.
Please, can you think of a better solution than mine? Thanks!
/** View that can be pressed like a button */
import UIKit
class ButtonView : UIView, UIGestureRecognizerDelegate {
static let NoChange = { (btn:ButtonView) in }
var enabled = true
/* Called when the view goes to normal state (set desired appearance) */
var onNormal = NoChange
/* Called when the view goes to pressed state (set desired appearance) */
var onPressed = NoChange
/* Called when the view is released (perform desired action) */
var action = {}
override init(frame: CGRect)
{
super.init(frame: frame)
let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(touched))
recognizer.delegate = self
recognizer.minimumPressDuration = 0.0
addGestureRecognizer(recognizer)
userInteractionEnabled = true
}
override func layoutSubviews() {
super.layoutSubviews()
onNormal(self)
}
func touched(sender: UILongPressGestureRecognizer)
{
guard enabled else { return }
print("Current state: \(sender.state.rawValue)")
if sender.state == .Began {
onPressed(self)
} else if sender.state == .Ended {
onNormal(self)
action()
} else if sender.state == .Changed {
onNormal(self)
// This cancels recognizer when dragging
// TODO: but doesn't drag scroll view
sender.enabled = false
sender.enabled = true
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
let button = ButtonView()
// add some views to the ButtonView, e.g. add a label
button.onNormal = { $0.backgroundColor = normalColor }
button.onPressed = { $0.backgroundColor = pressedColor }
button.action = { doSomethingWhenButtonIsClicked() }
Another solution with UIControl. It reacts slower but it's not too bad, and you can drag a UIScrollView below.
/**
* View that can be pressed like a button.
*
* Note that, since UIView has userInteractionEnabled = true by default,
* you should disable it for UIView layers that you put insde this ButtonView.
* Otherwise the ButtonView will not receive the touch events.
*/
// See: http://stackoverflow.com/q/40726283/custom-button-view
import UIKit
class ButtonView : UIControl {
static let NoChange = { (btn:ButtonView) in }
/* Called when the view goes to normal state (set desired appearance) */
var onNormal = NoChange
/* Called when the view goes to pressed state (set desired appearance) */
var onPressed = NoChange
/* Called when the view goes to disabled state, i.e. enabled = false (set desired appearance) */
var onDisabled = NoChange
/* Called when the view is released (perform desired action) */
var action = {}
override init(frame: CGRect)
{
super.init(frame: frame)
self.addTarget(self, action: #selector(pressed), forControlEvents: .TouchDown)
self.addTarget(self, action: #selector(clicked), forControlEvents: .TouchUpInside)
self.addTarget(self, action: #selector(cancelled), forControlEvents: [.TouchCancel, .TouchDragExit, .TouchUpOutside])
userInteractionEnabled = true // this is the default!
}
override func layoutSubviews() {
super.layoutSubviews()
updateBaseStyle()
}
func pressed() {
onPressed(self)
}
func clicked() {
onNormal(self)
action()
}
func cancelled() {
onNormal(self)
}
override var enabled: Bool {
willSet(e) {
super.enabled = e
updateBaseStyle()
}
}
func updateBaseStyle() {
if self.enabled {
onNormal(self)
} else {
onDisabled(self)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage is similar (I just added onDisable):
let button = ButtonView()
// add some views to the ButtonView, e.g. add a label
// note child views should have userInteractionEnabled = false
button.onNormal = { $0.backgroundColor = normalColor }
button.onPressed = { $0.backgroundColor = pressedColor }
button.onDisabled = { $0.backgroundColor = disabledColor }
button.action = { callToAction() }

Custom UIView with tapGesture

I have a drawing app. Inside my VC there are five imageViews with five colors in them. I want to be able to click on the imageView and change the stroke color. It can be easily done if I repeat myself in the viewcontroller by adding gesture Recognizers to each UIImageView and have their individual "selector" function. Such as
func redTapped() {}
func blueTapped() {}
However, I want to be able to make the code more clear by creating a custom class (ColorImageView.Swift) for these ImageViews so that when I assign the class to these buttons, they automatically gets the tap gesture and my VC automatically receives the information about which one is tapped. At the moment, I can get a "imagePressed" printed out for each image that gets assigned to my class. However, I have no way of distinguishing which one were pressed. Below are my code for ColorImageView.Swift
import Foundation
class ColorImageView: UIImageView {
private func initialize() {
let touchGesture = UITapGestureRecognizer(target: self, action: #selector(ColorImageView.imagePressed(_:)))
touchGesture.numberOfTapsRequired = 1
self.userInteractionEnabled = true
self.addGestureRecognizer(touchGesture)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func imagePressed(gestureRecognizer: UITapGestureRecognizer) {
print("image pressed \(gestureRecognizer)")
}
}
My imageView names are red.png, green.png, blue.png...etc
Thanks
You can get the tag easily.It works fine.
func imagePressed(gestureRecognizer: UITapGestureRecognizer)
{
print("image pressed \(gestureRecognizer)")
let tappedImageVIew = gestureRecognizer.view as! UIImageView
print("image pressed \(tappedImageVIew.tag)")
}

Resources