I have created a circle using Bezier path as well as i created tap events to check whether the layer has been tapped or not, it works fine until i increase the size of lineWidth of CAShapeLayer. By tapping on the lineWidth, sometimes it is detected and sometimes it is not detected.
I searched stackoverflow about the problem which i was having but i am unable to solve it. The problem remains the same, i am unable to detect certain taps on my layer(lineWidth area).
I just want to detect taps on the lineWidth of CAShapeLayer, i have been searching everywhere but couldn't find a proper solution for it. Most of the answers are in outdated languages. I would really appreciate if anyone could give an example to solve my issue. Swift 4.
https://i.imgur.com/194Aljn.png
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapDetected(tapRecognizer:)))
self.addGestureRecognizer(tapRecognizer)
#objc public func tapDetected(tapRecognizer:UITapGestureRecognizer) {
let tapLocation:CGPoint = tapRecognizer.location(in: self)
self.hitTest(tapLocation: CGPoint(x: tapLocation.x, y: tapLocation.y))
}
private func hitTest(tapLocation:CGPoint) {
if layer.path?.contains(tapLocation) == true {
print("Do something")
} else {
print("Nothing Found")
}
}
The problem is that the line stroke is not really part of the path - is it is just parts of its display. You can convert the path to be an larger path containing the stroke by using some CGPath methods:
let pathWithLineStroke = UIBezierPath.init(cgPath: path.cgPath.copy(strokingWithWidth: 2.0, lineCap: CGLineCap.butt, lineJoin: .bevel, miterLimit: 1.0));
Of course replace the the width, lineCap, lineJoin, and miterLimit with your actual values.
I'd recommend doing this earlier in your code, and then just drawing the path that already has the strokes built in, instead of setting those properies on the CALayer.
Hope that helps. Good luck.
Related
Hope all are safe in this pandemic situation.
I have been trying to design an application similar to SnapChat, I am stucked in an issue since a week for which I am not getting any library from gitHub or any code reference from stackOverFlow.
I have to draw various emoji lines using swipe gesture on an UIImageView for which I have used UIGraphicsGetCurrentContext()
The issue I am facing is to managing equal space between the emojis.
I am attaching the screenshot of my work done till now.
Any help would be appreciated.
Thanks in advance
func drawLineFrom(_ fromPoint: CGPoint, toPoint: CGPoint) {
// isDrawingEmoji is used as a boolean to manage simple line drawing or emoji drawing.
if isDrawingEmoji == true {
UIGraphicsBeginImageContext(topImageView.frame.size)
let context = UIGraphicsGetCurrentContext()
context?.setAlpha(1.0)
topImageView.image?.draw(in: self.img.frame)
if let emoji = selectedEmoji.cgImage {
context?.draw(emoji , in: CGRect(x: toPoint.x , y: toPoint.y, width: 50, height: 50))
}
topImageView.clipsToBounds = true
topImageView.image = UIGraphicsGetImageFromCurrentImageContext()
topImageView.alpha = 1.0
UIGraphicsEndImageContext()
}
I assume you are using a pan gesture recognizer rather than a swipe gesture recognizer?
You need to write code that takes the points you receive from the gesture recognizer and treat them as the endpoints of a line segment. You would then apply your emoji evenly along the space between the points at a regular interval.
If you want more detailed help than that you will need to post your current drawing code.
I have a short line (MKPolyline) and a custom annotation class (MKPointAnnotaion). Right now I have the point annotation located at the midpoint of the polyline. However, I would like the callout to be displayed whenever any point on the polyline is touched, similar to how routing performs in Maps. Since polylines don't seem to have a touchable attribute like annotations, is there a way to have the annotation encompass the entire line?
I do not care about the annotations marker (it is actually a nuisance and would prefer to hide it), only the callout and associated class info.
If at all possible could someone provide a brief example code with the answer?
EDIT: Other posts seem to be from 5+ years ago with links that do not work anymore
A couple of thoughts:
I think your “add annotation” approach for where you want this callout to start from is going to be the easiest approach. If you start contemplating making an annotation view subclass that shows the path, I think that’s going to get pretty hairy pretty quickly (handling scaling changes, rotations, 3D, etc.). Overlays give you a bunch of behaviors for free, so I think you’ll want to stick with that. And annotations give you callout behaviors for free, too, so I think you’ll want to stay with that too.
If you don’t want your annotation view to show a pin/marker, just don’t subclass from MKPinAnnotationView or MKMarkerAnnotationView, but rather just use MKAnnotationView.
The tricky step is how to detect taps on the MKPolylineRenderer. One idea is to create a path that traces the outline of the path of the MKPolyline.
extension MKPolyline {
func contains(point: CGPoint, strokeWidth: CGFloat = 44, in mapView: MKMapView) -> Bool {
guard pointCount > 1 else { return false }
let path = UIBezierPath()
path.move(to: mapView.convert(from: points()[0]))
for i in 1 ..< pointCount {
path.addLine(to: mapView.convert(from: points()[i]))
}
let outline = path.cgPath.copy(strokingWithWidth: strokeWidth, lineCap: .round, lineJoin: .round, miterLimit: 0)
return outline.contains(point)
}
}
where
extension MKMapView {
func convert(from mapPoint: MKMapPoint) -> CGPoint {
return convert(mapPoint.coordinate, toPointTo: self)
}
}
You can then create a gesture recognizer that detects a tap, checks to see if it’s within this path that outlines the MKPolyline, or whatever. But these are the basic pieces of the solution.
Obviously, the answers here outline different, apparently looking at the distance to the paths. That conceivably would work, too.
I am transferring an app for iOS to a toolbar app in OSX. Although I am using swift, the objects are rather different. I am having one major problem, however, that I cannot seem to overcome.
The code below produces the NSButton shown, but I cannot get rid of the grey background. masktobound has no effect. I have tried masking each corner individually, no effect. I just want a simple round button.
I also have several buttons with rounded corners, however these also show the light grey background.
Any pointers? Sample code would be appreciated.
let connectButton = NSButton.init(title: NSLocalizedString(" ", comment: "OnButtonAccessibility"), target: self, action: #selector(toggle))
connectButton.wantsLayer = true
connectButton.isBordered = false
connectButton.layer?.masksToBounds=true
if #available(OSX 10.13, *) {
connectButton.layer?.maskedCorners=[.layerMaxXMaxYCorner]
} else {
connectButton.layer?.backgroundColor = NSColor.blue.cgColor
connectButton.layer?.cornerRadius=80
connectButton.layer?.borderColor=DarkerBlue.cgColor
connectButton.layer?.borderWidth=3
(connectButton.cell as! NSButtonCell).isBordered=false
(connectButton.cell as! NSButtonCell).backgroundColor=NSColor.clear
connectButton.isTransparent=true
connectButton.frame=CGRect(x: 80, y: self.view.frame.size.height-260, width: 160, height: 160)
connectButton.tag=1002
self.view.addSubview(connectButton)
It appears that you must at least touch the NSButton.cell to have it conform to the button style. The backgroundstyle used seems to make no difference is you have the button filled or containing an image.
connectButton.cell?.backgroundStyle=NSView.BackgroundStyle.dark
In iOS 11, I have a PDFView controller implementation that allows to annotate a PDF, and one of those annotation if free-form drawing using PDFAnnotationSubtypeInk
let page : PDFPage = ...
let points : [CGPoint] = ...
let path = UIBezierPath()
for x in 0..<points.count {
let point = self.pdfView.convert(points[x], to: page)
if x == 0 {
path.move(to: point)
}else{
path.addLine(to: point)
}
}
let border = PDFBorder()
border.style = .solid
border.lineWidth = 2.0
let drawAnnotation = PDFAnnotation(bounds: page.bounds(for: .mediaBox), forType: .ink, withProperties: nil)
drawAnnotation.backgroundColor = .yellow
drawAnnotation.interiorColor = .yellow
drawAnnotation.fontColor = .yellow
drawAnnotation.color = .yellow
drawAnnotation.border = border
drawAnnotation.add(path)
page.addAnnotation(drawAnnotation)
When I call the persistence code
if let path = self.pdfDocumentPath, let document = self.pdfDocument {
if !document.write(toFile: path){
NSLog("Failed to save PDF")
}
}
Everything works in theory, the PDF is saved to disk... But on the next-reload from disk, my annotation is nowhere to be seen. Other types of annotations are saved correctly (i.e. Text and Highlight)
It looks like the UIBezierPaths never makes it back from Disk. No UIBezierPath is to be found in the reloaded PDFAnnotation
Am I doing something wrong? Am I missing something?
PS: I am sorry for Ruby devs that are looking for questions/answers on the Ruby Package known as PDFKit... iOS also have a package named PDFKit since iOS 11... Sorry for any confusion.
I hacked my way around the problem. The annotation get saved, but the UIBezierPath does not (and all it's points)
So when I write the PDF back to disk, I go through all the pages, and all annotations, and find those with UIBezierPath, and serialize the CGPoints into JSON into the PDFAnnotation content property.
When reloading the same PDF, I do the exact same thing, which is go through all the pages, and find .ink annotation, deserialize the points, and add the UIBezierPath back on the annotation.
Apple fixed this bug in iOS 12. PDFAnnotation.paths is filled correctly.
Starting with some version between 11.2 - 11.4 (I do not have the opportunity to verify, but it does not work exactly in 11.1 and works in 11.4+) you can use PDFAnnotation method drawWithBox:inContext: for rendering ink annotation correctly.
Attention, in iOS 12.0 Apple broke PDFAnnotation.color (alpha is always equal 1 after save-reload PDFDocument from disk). Can be repaired by drawing an annotation through the context with method above.
So i'm using a spritenode with an image and I don't set the position of it. Funny enough I was having issues setting the position of it so I didn't and it by default was set to center of page (for obvious reason) which ended up being perfect. So now in my touchesbegan method i'm looking to change the said image IF the original image was pressed so i'm checking if the touched location is equal to the node (of the image)'s position. Then if that is true, I replace it with the new image which is called "nowaves".
for touch in touches
{
let location = touch.location(in: self)
if (location == audioPlaying.position)
{
audioPlaying = SKSpriteNode(imageNamed: "nowaves")
}
else
{
Do you guys think I need to readd it? Well right as I asked that I tested it to no result. This is what I tried:
audioPlaying.size = CGSize(width: frame.size.width / 13, height: frame.size.height / 20)
addChild(audioPlaying)
If anyone has any idea what the problem is I would love some feedback, thank you :D
By default, the position property returns the coordinates of the center of the node. In other words, unless you touched the exact center of the sprite, if (location == audioPlaying.position) will never be true. However, you should be aware that touching a node's exact center point is practically impossible.
So we use another method. We just need to check if the touched node is the node you want.
let touchedNode = self.nodes(at: touch.location(in: self)).first
if touchedNode == audioPlaying {
// I think setting the texture is more suitable here, instead of creating a new node.
audioPlaying.texture = SKTexture(imageNamed: "nowaves")
}
To add on to #sweeper ,
I had all my initial image content in the scenedidLoad method, rather than that I created a new method that I was already using for another functionality to start the game and had the initial image there instead and now everything works well.