How to draw emojis on UIImageView using gesture - swift

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.

Related

Add a popup text view to a SpriteKit game

I am creating a SpriteKit game and want to be able to take advantage of textAlerts - like all the games I grew up playing. I also want them to be flexible so I can subclass them and add them throughout my game - I am not sure how to do this and so would be very grateful of any advice.
There are a number of ways that I have started to look into:
Use another SKScene
The idea here would be to add another scene immediately on top of our current one. This would have a faded out background and would be configured with the text to be displayed. This seems like the wrong approach
Use an SKNode
I could create a custom node and initialise it with my specific text. I would then disable movement and add the node at the bottom of the screen. I would then customise the actions that occur when it is tapped.
Use an SKLabel
SKLabels are designed to show text so these seem like a promising place to look. The issue is that I want to add an image into the popup view (this a headshot of the person you are talking to) and so it doesn't feel like I should be able to inject an image in.
Use something else
I don't know what this might be. Is what I am trying to do easy or harder than I think?
My problem is that I come from a swift background so am struggling to convert to a SpriteKit mindset. In Swift I would put together a custom UIView with all the UIKit components and add it to the screen. In SpriteKit we don't have the same tools so I don't know the right combinations to put together what I want.
Ideally I want to be able to configure the text popup with an array of text so that a conversation can be had with the user tapping for each new line.
Edit 1: What I have tried so far
Based on #ElTomatos comment I have experimented with adding a SKSpriteNode and an SKLabelNode on top of each other using the following code:
guard let camera = camera else { return }
let dialogueBackground = SKSpriteNode(texture: nil, color: .red, size: CGSize(width: screenSize.width / camera.xScale, height: screenSize.height / 3 / camera.xScale))
dialogueBackground.anchorPoint = CGPoint(x: 0, y: 0)
dialogueBackground.zPosition = 50
dialogueBackground.position = CGPoint(x: -screenSize.width, y: -screenSize.height)
camera.addChild(dialogueBackground)
let label = SKLabelNode(text: "Hello world")
label.zPosition = 100
label.position = CGPoint(x: -screenSize.width, y: 100 - screenSize.height)
camera.addChild(label)
As you can see the view doesn't go to the edge of the screen. Is this something to do with the safe area? It's difficult to get the width and height as the camera doesn't have a CGSize we can use.

Taps events not getting detected on CAShapeLayer lineWidth?

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.

NSButton not masking to bounds

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

Transform a view by dragging its edge (Swift)

I try to make an app where the user can draw or add an arrow, and transform this arrow (translate, rotate, ...).
For the moment, I manage to draw the arrow and make all the transformations on it, but what I would like to do now is to be able to modify the arrow by dragging its edges.
To draw an arrow, I just create a UIView with a height of 20px (it is the thickness of the arrow), and a width of 400 (length of the arrow).
func drawArrow(frame: CGRect) {
let thicknessArrow = 20
let viewHorizontalArrow = UIView()
viewHorizontalArrow.frame.origin = CGPoint(x: 100, y: 600)
viewHorizontalArrow.frame.size = CGSize(width: 400, height: thicknessArrow)
drawDoubleArrow(viewHorizontalArrow, startPoint: CGPoint(x: 0, y: thicknessViewWithArrow/2), endPoint: CGPoint(x: viewHorizontalArrow.frame.width, y: thicknessViewWithArrow/2), lineWidth: 10, color: UIColor.blackColor())
}
After that, I transform the UIView thanks to the pan, pinch and rotate gesture.
The function "drawDoubleArrow" create an arrow with BezierPath and add it to the layer of the UIView.
I hope these explanations are clear enough :).
Could you help me to find a solution ?
Thanks !
UIBezierPath on it's own cannot easily be manipulated by gestures because it doesn't recognize them. But a UIView can recognize gestures. Combined with a CALayer's ability to be transformed and it's hitTest() method, I think you can achieve what you want.
// I'm declaring the layer as a shape layer, but depending on how your error thick is, you may need a rectangular one
let panGesture = UIPanGestureRecognizer()
var myLayer = CAShapeLayer()
viewDidLoad:
// I'm assuming you've created myBezierPath, an arrow path
myLayer.path = myBezierPath.cgPath
// Adding the pan gesture to the view's controller
panGesture.addTarget(self, action: #selector(moveLayer))
self.addGestureRecognizer(panGesture)
moveLayer:
func moveLayer(_ recognizer:UIPanGestureRecognizer) {
let p = recognizer.location(in: self)
// You will want to loop through all the layers you wish to transform
if myLayer.hitTest(p) != nil {
myLayer.position = p
}
}

How would I make a simple spinning dash in Swift? that spins on its center like a loader in terminal

I'm trying to create a simple spinning loading dash. I know how to do the loop but I can't seem to make it on a single line. Any ideas?
let loop = 1
while loop > 0 {
// spinning dash
}
I will not provide you with all the code to your question but rather a guideline of what to do. In general, its a two step algorithm.
Draw a line
Perform a 360° rotation of it for a desired time, t
The code posted below implements the first portion. I have added comments and I believe it should be self explanatory. After reviewing it, I'd recommend you read about UIBezierPath.
As for the second part, there are two ways of going about this.
1. Rotate the line itself (recommended)
Should you choose this method, here's a tutorial from Ray Wenderlich which covers it extensively along with the Math behind it. Follow through both portions of the tutorial if possible.
2. Rotate the view encompassing the line
Changing the outer view's background color to clear then rotating itself will give the illusion that the line inside is the one rotated. Here's a video guide for view rotations.
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// This is the black subview in which the line will be drawn into
let lineView: GeneralDraw = GeneralDraw(frame: CGRect(origin: CGPoint(x: 20, y: 30), size: CGSize(width: 300, height: 300)))
// uncomment this to remove the black colour
// lineView.backgroundColor = .clear
// add this lineView to the mainView
self.view.addSubview(lineView)
}
}
// This handles the drawing inside a given view
class GeneralDraw: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect)
{
let linePath = UIBezierPath()
// start point of the line
linePath.move(to: CGPoint(x:50, y:10))
// end point of the line
linePath.addLine(to: CGPoint(x:200, y:10))
linePath.close()
// cosmetic settings for the line
UIColor.red.set()
linePath.stroke()
linePath.fill()
}
}
I would use a CAReplicatorLayer for this. You start with a layer that draws a horizontal bar and combine it with transforms that show the bar in the other positions. Then you animate the fading out of the bar, with an offset coordinated to the fading.
In this gif, I've deliberately slowed down the animation. (There is a mild glitch at the point where the gif repeats, but ignore that; the real project doesn't have that glitch.)
1. Solution: rotate Images
create a set of images which shows the dash rotating.
set the images to an array. then animate that `UIImageView.startAnimating()
see section "Animating a Sequence of Images" of UIImageView.
2. Solution: standard iOS activity indicator
But better go with the standard UIActivityIndicatorView
see also:
iOS Human Interface Guidelines: Progress Indicators
Reference for UIActivityIndicatorView