How to move line? - ios

How to move horizontal line in a vertical direction?
I can draw a straight horizontal line and I need to move it using long press.
How can I do that?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let currentPoint = touch.location(in: self.view)
DrawLine(FromPoint: currentPoint, toPoint: currentPoint)
}
}
func DrawLine(FromPoint: CGPoint, toPoint: CGPoint)
{
UIGraphicsBeginImageContext(self.view.frame.size)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
let context = UIGraphicsGetCurrentContext()
let lineWidth : CGFloat = 5
context?.move(to: CGPoint(x: FromPoint.x, y: FromPoint.y))
context?.addLine(to: CGPoint(x: FromPoint.x + 200, y: FromPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(lineWidth)
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}

you can draw a horizontal line using UIView and can move it in vertical direction using UIView.animate method by providing the required options.
In your ViewController define the following variables:
var scanlineRect = CGRect.zero
var scanlineStartY: CGFloat = 0
var scanlineStopY: CGFloat = 0
var topBottomMargin: CGFloat = 30
var scanLine: UIView = UIView()
Call drawLine method inside viewDidLoad method and call moveVertically method on your touch event:
func drawLine() {
self.addSubview(scanLine)
scanLine.backgroundColor = UIColor(red: 0.4, green: 0.8, blue: 0.4, alpha: 1.0) // green color
scanlineRect = CGRect(x: 0, y: 0, width: self.frame.width, height: 2)
scanlineStartY = topBottomMargin
scanlineStopY = self.frame.size.height - topBottomMargin
}
func moveVertically() {
scanLine.frame = scanlineRect
scanLine.center = CGPoint(x: scanLine.center.x, y: scanlineStartY)
scanLine.isHidden = false
weak var weakSelf = scanLine
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse, .beginFromCurrentState], animations: {() -> Void in
weakSelf!.center = CGPoint(x: weakSelf!.center.x, y: scanlineStopY)
}, completion: nil)
}

what you can do is to draw this line on a UIView, and move the UIView upwards or to any position you want like so:
UIView.animate(withDuration: 0.3, animations: {
yourView.origin = CGPoint(yourView.frame.origin.x, yourYPosition)
}, completion: { (value: Bool) in
//do anything after animation is done
})

Related

How to animate UIBezierPath inside of draw(_:)?

I have a task to draw rectangle in draw(_:) method and to animate height of it.
var height = 0
override func draw(_ rect: CGRect) {
let rect = CGRect(x: 0, y: 0, width: 100, height: height)
let rectanglePath = UIBezierPath(rect: rect)
UIColor.green.setFill()
rectanglePath.fill()
}
And I'm looking to animate something like this:
func animate() {
UIView.animate(withDuration: 2) {
self.height = 300
}
}
I also can use CAShapeLayer and CABasicAnimation, but I don't know how to draw it in draw(_:) method. Using draw(_:) method is required in this task :(
class View: UIView{
override func draw(_ rect: CGRect)
{
let rectanglePath = UIBezierPath(rect: self.frame)
UIColor.green.setFill()
rectanglePath.fill()
}}
class ViewController: UIViewController{
override func viewDidLoad()
{
super.viewDidLoad()
let subview = View(frame: CGRect(x: 0, y: 0, width: 100, height: 0))
self.view.addSubview(subview)
UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 2,
delay: 0,
options: .curveLinear,
animations: {self.view.subviews[0].frame = CGRect(x: 0, y: 0, width: 100, height: 100)}
)
}}

Is this the correct way to draw on a UIImageview

I am trying to make an app that draw on screen following my finger. I took pieces from several websites and manage to get it done. screenshot app screen
The app is working , I can choose different colors and line is following my finger. Only thing is that when i use it on a real device (iphone SE and Xs Max) after a bunch of lines my app crash with the message iOS terminate on memory.
this is not happening with the simulator. I am not experienced enough to use instruments. Any idea what can cause this ?
maybe something wrong in the function :
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
Or has a iPhone just not enough memory to draw on a full screen, then I am wasting my time searching for a solution that has no solution?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var UIImage: UIImageView!
#IBOutlet weak var tempImageView: UIImageView!
#IBOutlet weak var colourInUse: UILabel!
var lastPoint = CGPoint.zero
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var brushWidth: CGFloat = 2.0
var opacity: CGFloat = 1.0
var swiped = false
let colors: [(CGFloat, CGFloat, CGFloat)] = [
(0, 0, 0),
(105.0 / 255.0, 105.0 / 255.0, 105.0 / 255.0),
(1.0, 0, 0),
(0, 0, 1.0),
(51.0 / 255.0, 204.0 / 255.0, 1.0),
(102.0 / 255.0, 204.0 / 255.0, 0),
(102.0 / 255.0, 1.0, 0),
(160.0 / 255.0, 82.0 / 255.0, 45.0 / 255.0),
(1.0, 102.0 / 255.0, 0),
(1.0, 1.0, 0),
(1.0, 1.0, 1.0),
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
UIImage.isUserInteractionEnabled = true
tempImageView.isUserInteractionEnabled = true
colourInUse.text = "Black"
}
#IBAction func pencilPressed(sender: AnyObject) {
var index = sender.tag ?? 0
if index < 0 || index >= colors.count {
index = 0
}
(red, green, blue) = colors[index]
if index == colors.count - 1 {
opacity = 1.0
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(UIImage.frame.size)
UIImage.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: 1.0)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
UIImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0)
let drawContext = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
drawContext?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
drawContext?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
drawContext?.setLineCap(CGLineCap.round)
drawContext?.setLineWidth(brushWidth)
drawContext?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
drawContext?.setBlendMode(CGBlendMode.normal)
drawContext?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
}

Swift Animate lines with dots

I have created multiple circles (points) using CAShapeLayer and multiple lines connecting to those circles. Then i animate those circles. what i want to do is animate connected lines along with dots. But only circles are animating not the lines. How can i achieve this? is there a way to update lines position?`
`
var points: [CAShapeLayer] = []
let positions: [CGPoint] = [CGPoint(x: 70, y: 100),
CGPoint(x: 140, y: 100),
CGPoint(x: 210, y: 100),
CGPoint(x: 70, y: 200),
CGPoint(x: 140, y: 200),
CGPoint(x: 210, y: 200)]
var lines:[CAShapeLayer] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
createPoints()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func createPoints(){
for (index,position) in positions.enumerated() {
let point = CAShapeLayer()
point.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 10, height: 10), cornerRadius: 5).cgPath
point.frame = CGRect(x: position.x, y: position.y, width: 10, height: 10)
point.fillColor = UIColor.red.cgColor
points.append(point)
view.layer.addSublayer(points[index])
}
animatePosition(point: points[0])
drawLines()
}
func drawLines(){
for (index,_) in positions.enumerated() {
let path = UIBezierPath()
//Not completed
//set line's start position to dots position
path.move(to: points[index].position)
//set line's end position to dots position
path.addLine(to: points[index+1].position)
let line = CAShapeLayer()
line.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
line.strokeColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1).cgColor
line.lineWidth = 1
line.strokeStart = 0
line.path = path.cgPath
lines.append(line)
view.layer.addSublayer(line)
}
}
func animatePosition(point: CAShapeLayer) {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = [10, 10 ]
animation.toValue = [100, 100]
animation.autoreverses = true
animation.repeatCount = .infinity
animation.isAdditive = true
point.add(animation, forKey: "pointStart")
// line.add(animation, forKey: "positionLine")
self.view.setNeedsLayout()
}
What i want is some thing like this:

Swift: Draw on image without changing its size (Aspect fill)

I have a simple view controller where the entire screen is an imageView. The imageView has its content mode set to UIViewContentMode.scaleAspectFill. I can draw on the picture, but as soon as I do the image shrinks horizontally. This messing up the quality of the image and I'm not sure exactly why it's happening.
I looked at this question, but I didn't understand the only answer. I think the issue is with the way I am drawing and that rect is shrinking the image.
class AnnotateViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var lastPoint = CGPoint.zero
var fromPoint = CGPoint()
var toPoint = CGPoint.zero
var brushWidth: CGFloat = 5.0
var opacity: CGFloat = 1.0
#IBAction func startDrawing(_ sender: Any) {
self.isDrawing = !self.isDrawing
if isDrawing {
self.imageView.isUserInteractionEnabled = true
showColorButtons()
}
else {
self.imageView.isUserInteractionEnabled = false
hideColorButtons()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastPoint = touch.preciseLocation(in: self.imageView)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, false, 0.0)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: 255, green: 0, blue: 0, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.preciseLocation(in: self.imageView)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
}
UPDATE - SOLUTION
I finally got back to working on this problem. Looking at this question, I was able to use the accepted answer to solve my problem. Below is my updated code:
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, true, 0.0)
let aspect = imageView.image!.size.width / imageView.image!.size.height
let rect: CGRect
if imageView.frame.width / aspect > imageView.frame.height {
let height = imageView.frame.width / aspect
rect = CGRect(x: 0, y: (imageView.frame.height - height) / 2,
width: imageView.frame.width, height: height)
} else {
let width = imageView.frame.height * aspect
rect = CGRect(x: (imageView.frame.width - width) / 2, y: 0,
width: width, height: imageView.frame.height)
}
imageView.image?.draw(in: rect)
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: self.red, green: self.green, blue: self.blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
annotatedImage = imageView.image
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
This line:
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
Draws the image at the aspect ratio of the screen, but your image is probably not at the same aspect ratio (the ratio of width to height).
I think this might be useful: UIImage Aspect Fit when using drawInRect?

I took a github project where he added a button programatically, but I added it with the interface builder but it doesn't work

Can Someone help me with that github project how to implement in my project with some code
This code is the one where I created the button using IB builder and created an outlet for the button and changed its class to a custom class and added the toggle function and the .TouchUpInside line
And I get an error
fatal error: unexpectedly found nil while unwrapping an Optional value
2017-07-03 00:51:21.630176+0530 ala[867:183071] fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
when I tap on the button and in its default state its not even visible
import UIKit
class serchViewController: UIViewController {
#IBOutlet weak var menuView: UIView!
#IBOutlet weak var menuBtn: HamburgerButton!
override func viewDidLoad() {
super.viewDidLoad()
let screenSize: CGRect = UIScreen.main.bounds
menuView.frame = CGRect (x: 0, y: 0, width: screenSize.width/4, height: screenSize.height)
self.menuBtn.addTarget(self, action: #selector(serchViewController.toggle(_:)), for: .touchUpInside)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func toggle(_ sender: AnyObject!) {
self.menuBtn.showsMenu = !self.menuBtn.showsMenu
}
}
This is the original code
import UIKit
class ViewController: UIViewController {
var button: HamburgerButton! = nil
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(red: 38.0 / 255, green: 151.0 / 255, blue: 68.0 / 255, alpha: 1)
self.button = HamburgerButton(frame: CGRect(x: 133, y: 133, width: 54, height: 54))
self.button.addTarget(self, action: #selector(ViewController.toggle(_:)), for:.touchUpInside)
self.view.addSubview(button)
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
func toggle(_ sender: AnyObject!) {
self.button.showsMenu = !self.button.showsMenu
}
}
the error file code is below
import CoreGraphics
import QuartzCore
import UIKit
class HamburgerButton : UIButton {
let shortStroke: CGPath = {
let path = CGMutablePath()
path.move(to: CGPoint(x: 2, y: 2))
path.addLine(to: CGPoint(x: 28, y:2))
return path
}()
let outline: CGPath = {
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 27))
path.addCurve(to: CGPoint(x: 40, y: 27), control1: CGPoint(x: 12, y: 27), control2: CGPoint(x: 28.02, y: 27))
path.addCurve(to: CGPoint(x: 27, y: 02), control1: CGPoint(x: 55.92, y: 27), control2: CGPoint(x: 50.47, y: 2))
path.addCurve(to: CGPoint(x: 2, y: 27), control1: CGPoint(x: 13.16, y: 2), control2: CGPoint(x: 2, y: 13.16))
path.addCurve(to: CGPoint(x: 27, y: 52), control1: CGPoint(x: 2, y: 40.84), control2: CGPoint(x: 13.16, y: 52))
path.addCurve(to: CGPoint(x: 52, y: 27), control1: CGPoint(x: 40.84, y: 52), control2: CGPoint(x: 52, y: 40.84))
path.addCurve(to: CGPoint(x: 27, y: 2), control1: CGPoint(x: 52, y: 13.16), control2: CGPoint(x: 42.39, y: 2))
path.addCurve(to: CGPoint(x: 2, y: 27), control1: CGPoint(x: 13.16, y: 2), control2: CGPoint(x: 2, y: 13.16))
return path
}()
let menuStrokeStart: CGFloat = 0.325
let menuStrokeEnd: CGFloat = 0.9
let hamburgerStrokeStart: CGFloat = 0.028
let hamburgerStrokeEnd: CGFloat = 0.111
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
for layer in [ self.top, self.middle, self.bottom ] {
layer?.fillColor = nil
layer?.strokeColor = UIColor.blue.cgColor
layer?.lineWidth = 4
layer?.miterLimit = 4
layer?.lineCap = kCALineCapRound
layer?.masksToBounds = true
let strokingPath = CGPath(__byStroking: (layer?.path!)!, transform: nil, lineWidth: 4, lineCap: .round, lineJoin: .miter, miterLimit: 4)
layer?.bounds = (strokingPath?.boundingBoxOfPath)!
layer?.actions = [
"strokeStart": NSNull(),
"strokeEnd": NSNull(),
"transform": NSNull()
]
self.layer.addSublayer(layer!)
}
self.top.anchorPoint = CGPoint(x: 28.0 / 30.0, y: 0.5)
self.top.position = CGPoint(x: 40, y: 18)
self.middle.position = CGPoint(x: 27, y: 27)
self.middle.strokeStart = hamburgerStrokeStart
self.middle.strokeEnd = hamburgerStrokeEnd
self.bottom.anchorPoint = CGPoint(x: 28.0 / 30.0, y: 0.5)
self.bottom.position = CGPoint(x: 40, y: 36)
}
var showsMenu: Bool = false {
didSet {
let strokeStart = CABasicAnimation(keyPath: "strokeStart")
let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
if self.showsMenu {
strokeStart.toValue = menuStrokeStart
strokeStart.duration = 0.5
strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
strokeEnd.toValue = menuStrokeEnd
strokeEnd.duration = 0.6
strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
} else {
strokeStart.toValue = hamburgerStrokeStart
strokeStart.duration = 0.5
strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0, 0.5, 1.2)
strokeStart.beginTime = CACurrentMediaTime() + 0.1
strokeStart.fillMode = kCAFillModeBackwards
strokeEnd.toValue = hamburgerStrokeEnd
strokeEnd.duration = 0.6
strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.3, 0.5, 0.9)
}
self.middle.ocb_applyAnimation(strokeStart)
self.middle.ocb_applyAnimation(strokeEnd)
let topTransform = CABasicAnimation(keyPath: "transform")
topTransform.timingFunction = CAMediaTimingFunction(controlPoints: 0.5, -0.8, 0.5, 1.85)
topTransform.duration = 0.4
topTransform.fillMode = kCAFillModeBackwards
let bottomTransform = topTransform.copy() as! CABasicAnimation
if self.showsMenu {
let translation = CATransform3DMakeTranslation(-4, 0, 0)
topTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, -0.7853975, 0, 0, 1))
topTransform.beginTime = CACurrentMediaTime() + 0.25
bottomTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, 0.7853975, 0, 0, 1))
bottomTransform.beginTime = CACurrentMediaTime() + 0.25
} else {
topTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
topTransform.beginTime = CACurrentMediaTime() + 0.05
bottomTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
bottomTransform.beginTime = CACurrentMediaTime() + 0.05
}
self.top.ocb_applyAnimation(topTransform)
self.bottom.ocb_applyAnimation(bottomTransform)
}
}
var top: CAShapeLayer! = CAShapeLayer()
var bottom: CAShapeLayer! = CAShapeLayer()
var middle: CAShapeLayer! = CAShapeLayer()
}
extension CALayer {
func ocb_applyAnimation(_ animation: CABasicAnimation) {
let copy = animation.copy() as! CABasicAnimation
if copy.fromValue == nil {
copy.fromValue = self.presentation()!.value(forKeyPath: copy.keyPath!)
}
self.add(copy, forKey: copy.keyPath)
self.setValue(copy.toValue, forKeyPath:copy.keyPath!)
}
}
Project GitHub link
error screenshot
IB builder screenshot
There is an open pull request on the linked project to make it usable from interface builder. Why not try using that fork instead of the original repo?
The reason it isn't working currently is that a lot of setup is being done in the init(frame:) method. That method doesn't get called when you create an object from the storyboard, init(coder:) is used instead. In the code above that method is empty, so none of the setup is done.
Normally you would put common setup code in a separate method and call it from both initialisers. That isn't the approach the PR author has taken, but you can always make your own fork.
As an example:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
// plus all the other code in here...
}
Would become:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonSetUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonSetUp()
}
private func commonSetUp() {
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
// plus all the other code in here...
}
Can you try changing #selector(serchViewController.toggle(_:)) to #selector(toggle(_:)). Looks like it's finding nil since you're trying to point it to a class func. You've said in the method that your target is self it should be pointing to a method in the current instance.

Resources