How to re-draw line connected between moveable two UIView - ios

I would like to re-draw the line between two movable UIView, depending on the position of UIView.
So I found this. However, this method uses "Pan Gesture" to re-draw lines, depending on the position of the gesture.
Also found are setNeedsDisplay(), but this is a request to re-draw, not a real-time event function.
The way I want to find it is not to use gestures to redraw lines, but to re-draw lines in real time.
In a little bit more detail, I applied "UIColisionBehavior" to all the UIVviews I created. The UIView applied changes position as they are struck, and depending on the changed position, the line is being redrawn.
As if the UIView were moving in this way, the connected line was redrawn according to where it was moved:
Below is the code I'm experimenting with in the Playground. When you execute the code, you can see that the first connected purple line is connected to the falling UIView and does not fall off:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MoveAbleView : UIView {
var outGoingLine : CAShapeLayer?
var inComingLine : CAShapeLayer?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func lineTo(connectedView: MoveAbleView) -> CAShapeLayer {
let path = UIBezierPath()
path.move(to: self.center)
path.addLine(to: connectedView.center)
let line = CAShapeLayer()
line.path = path.cgPath
line.lineWidth = 5
line.strokeColor = UIColor.purple.cgColor
connectedView.inComingLine = line
outGoingLine = line
return line
}
}
class MyViewController : UIViewController {
var dynamicAnimator = UIDynamicAnimator()
var collisionBehavior = UICollisionBehavior()
var gravityBehavior = UIGravityBehavior()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 100, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let gravityViw2 = MoveAbleView(frame: CGRect(x: 300, y: -200, width: 50, height: 50))
gravityViw2.backgroundColor = UIColor.blue
self.view.addSubview(gravityViw2)
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
collisionBehavior.addItem(gravityViw2)
gravityBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
self.view.layer.addSublayer(viw.lineTo(connectedView: viw2))
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
When UIView strikes and moves, how do you redraw a connected line in real time?

Actually, all what you need is another UIView to represent the lines and employ attachmentBehaviors. For instance, there is a line between two attached objects.
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
dynamicAnimator = UIDynamicAnimator(referenceView: view)
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 200, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let line1 = MoveAbleView(frame: CGRect(x: 125, y: 225, width: 200, height: 10))
line1.backgroundColor = UIColor.purple
self.view.addSubview(line1)
let l1 = UIAttachmentBehavior.init(item: viw, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: -100, vertical: 0))
let l2 = UIAttachmentBehavior.init(item: viw2, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: 100, vertical: 0))
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw)
dynamicAnimator.addBehavior(l1)
dynamicAnimator.addBehavior(l2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
}
}

Related

UIView frame slightly different when setting corner radius

I am drawing a sequence of UIViews that touch each other's edges.
But when I set view.layer.cornerRadius they get slightly out of alignment creating artifacts. Is there some way around this?
Minimal working example (Swift Playround):
It seems to depend on the floating point coordinates.
I'd prefer if there was a version that didn't require me to round the coordinates to whole integers, though.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
self.view.backgroundColor = .white
let rect11 = UIView(frame: CGRect(x: 100, y: 100.15, width: 100, height: 100))
let rect12 = UIView(frame: CGRect(x: 100, y: 200.15, width: 100, height: 100))
let rect21 = UIView(frame: CGRect(x: 300, y: 100.15, width: 100, height: 100))
let rect22 = UIView(frame: CGRect(x: 300, y: 200.15, width: 100, height: 100))
rect21.layer.cornerRadius = 10
rect22.layer.cornerRadius = 10
rect11.backgroundColor = .black
rect12.backgroundColor = .black
rect21.backgroundColor = .black
rect22.backgroundColor = .black
self.view.addSubview(rect11)
self.view.addSubview(rect12)
self.view.addSubview(rect21)
self.view.addSubview(rect22)
}
}
let controller = ViewController()
PlaygroundPage.current.liveView = UINavigationController(rootViewController: controller)

Touch event on Visible Portion of Views ios swift 4

I am working of Photo Collage which is in Swift4 , I had created collage using UIBezierPath as Below
I have 5 scroll views in Storyboard and the sequence of Scrollviews as Below
Using Following code I am creating Shapes :
var path1 = UIBezierPath()
path1.move(to: CGPoint(x: 0, y: 0))
path1.addLine(to: CGPoint(x: superView.frame.width / 2, y: 0))
path1.addLine(to: CGPoint(x: 0, y: superView.frame.width / 2))
path1.addLine(to: CGPoint(x: 0, y: 0))
var borderPathRef1 = path1.cgPath
var borderShapeLayer1 = CAShapeLayer()
borderShapeLayer1.path = borderPathRef1
scroll1.layer.mask = borderShapeLayer1
scroll1.layer.masksToBounds = true
var path2 = UIBezierPath()
path2.move(to: CGPoint(x: 0, y: 0))
path2.addLine(to: CGPoint(x: superView.frame.width / 2, y: 0))
path2.addLine(to: CGPoint(x: superView.frame.width / 2, y: superView.frame.width / 2))
path2.addLine(to: CGPoint(x: 0, y: 0))
var borderPathRef2 = path2.cgPath
var borderShapeLayer2 = CAShapeLayer()
borderShapeLayer2.path = borderPathRef2
scroll2.layer.mask = borderShapeLayer2
scroll2.layer.masksToBounds = true
Now the issue is I am not able to get touch event of Scrollviews as Scroll5 is on top. I want to get Touch on Overlapped views like Scroll1, Scroll2 and so on. In Short I need touch event for particular view on the portion of area where the view is visible.
See The Image Below Where I want Touch for Views.
How can I get touch on Overlapped Views?
Please Help!
You have to be sure that you set the superView of scrollview to isUserInterfaceEnabled=true first.
To get overlapped views touch event:
here is my code:
class CustomView: UIView {
let view1: UIView
let view2: UIView
let view3: UIView
let view4: UIView
let view5: UIView
override init(frame: CGRect) {
view1 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view1.backgroundColor = UIColor.black
view1.isUserInteractionEnabled = true
view2 = UIView(frame: CGRect(x: 100, y: 0, width: 100, height: 100))
view2.backgroundColor = UIColor.orange
view2.isUserInteractionEnabled = true
view3 = UIView(frame: CGRect(x: 0, y: 100, width: 100, height: 100))
view3.backgroundColor = UIColor.blue
view3.isUserInteractionEnabled = true
view4 = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view4.backgroundColor = UIColor.brown
view4.isUserInteractionEnabled = true
view5 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view5.tag = 121
view5.backgroundColor = UIColor.red
super.init(frame: frame)
self.addSubview(view1)
self.addSubview(view2)
self.addSubview(view3)
self.addSubview(view4)
self.addSubview(view5)
view1.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(touchAction)))
}
#objc func touchAction () {
print("----------- touch view 1")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if (view1.point(inside: self.convert(point, to: view1), with: event)) {
return view1
}
return self
}
}
The function hitTest decides that which view you are touching, so you just need to return the overlapped view.

IOS - How to create Facebook reaction bar with blur background?

Although it may not be the week to replicate some design of Facebook, I would like to be able to design my own version of the reaction indicator view below.
I have three UIImageViews lined in the same positions as above. The problem is that, unlike Facebook, the background color may change (i.e is on top of a UIBlurEffect) and therefore I am unable to set the border color to white.
I thought it would make sense to set the borderColor like so:
imageViewOne.layer.borderColor = UIColor.clear.cgColor
imageViewOne.layer.borderWidth = 2
However, the underlying imageViewTwo is displayed in the border instead of the background color.
So far, I have this:
Would appreciate some help/ideas on how to make this work - I'm thinking of masks but not sure whether a. this is the correct solution and b. how to achieve the desired effect. To clarify, I am not able to set the border color as a constant as it will change with the UIBlurEffect.
In my opinion, there are 2 way to resolve your problem.
Create and use clipped image for Wow and Love like below Love image.
Another way is using mask property of UIView. Creating a mask image and apply it for mask property.
Mask image looks like.
Code for applying mask.
let imvLoveMask = UIImageView.init(image: UIImage.init(named: "MASK_IMAGE_NAME"));
imvLoveMask.frame = CGRect.init(x: 0, y: 0, width: imvLove.frame.size.width, height: imvLove.frame.size.height);
imvLove.mask = imvLoveMask;
Both of 2 above way can help you achieve what you want in the question. Background of icons in below image is an UIVisualEffectView.
In my opinion, the first way with clipped image is better and faster because you don't need to apply mask for your imageView. But if you don't want to create a clipped image for some reason, you can use the second way.
For more detail, you can take a look at my demo repo
You need to clip part of the image in order to let underlying content be visible in the gaps between images. See playground sample.
Add smile_1, smile_2, smile_3 images to playground resources. I took emoji images from https://emojipedia.org/facebook/.
import UIKit
import PlaygroundSupport
class EmojiView: UIView {
var imageView = UIImageView()
var imageInset = UIEdgeInsets(top: 3.0, left: 3.0, bottom: 3.0, right: 3.0)
var shapeLayer = CAShapeLayer()
var overlap: CGFloat = 0.0 {
didSet {
self.updateShape()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: UIView
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageView.frame = UIEdgeInsetsInsetRect(self.bounds, self.imageInset)
self.shapeLayer.frame = self.bounds
self.updateShape()
}
// MARK: Private
private func setup() {
self.addSubview(self.imageView)
self.layer.mask = self.shapeLayer
}
private func updateShape() {
let frame = self.bounds
let path = UIBezierPath(rect: frame)
// Cut off part of the image if overlap more then > 0
if 0 < self.overlap {
path.usesEvenOddFillRule = true
path.append(UIBezierPath(ovalIn: CGRect(x: -frame.width + self.overlap, y: 0.0, width: frame.width, height: frame.height)).reversing())
}
self.shapeLayer.path = path.cgPath
}
}
let overlap: CGFloat = 10 // Amount of pixels emojis overlap each other
// Create emoji views
let emojiView_1 = EmojiView(frame: CGRect(x: 5.0, y: 5.0, width: 40.0, height: 40.0))
emojiView_1.imageView.image = UIImage(named: "smile_1")
let emojiView_2 = EmojiView(frame: CGRect(x: emojiView_1.frame.maxX - overlap, y: 5.0, width: 40.0, height: 40.0))
emojiView_2.imageView.image = UIImage(named: "smile_2")
emojiView_2.overlap = overlap
let emojiView_3 = EmojiView(frame: CGRect(x: emojiView_2.frame.maxX - overlap, y: 5.0, width: 40.0, height: 40.0))
emojiView_3.imageView.image = UIImage(named: "smile_3")
emojiView_3.overlap = overlap
let holderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: emojiView_3.frame.maxX + 5, height: 50.0))
// Add gradient layer
let gradientLayer = CAGradientLayer()
gradientLayer.frame = holderView.bounds
gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
holderView.layer.addSublayer(gradientLayer)
// Add emoji views
holderView.addSubview(emojiView_1)
holderView.addSubview(emojiView_2)
holderView.addSubview(emojiView_3)
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = holderView
use this :
self.imageViewOne.layer.cornerRadius = self.imageViewOne.layer.bounds.width/2
self.imageViewOne.layer.masksToBounds = true
Simple suggestion: As you are setting border color programatically, you have a control to change it, according to background color (if background color is solid (not a gradient)).
imageViewOne.layer.borderColor = imageViewOne.superview?.backgroundColor ?? UIColor.white
imageViewOne.layer.borderWidth = 2.0
Actually instead of masking, you can put your images in a view which has white background and round(set corner radius). Then you can put these views (which has white background and images in it) via settings their zPosition or on storyboard with view hierarchy.
I've prepared a little playground for you. You can see the result in the screenshot. I've put a view inside the containerViews instead you can use uiimageview etc. It's a bit ugly but solves your issue I guess it's up to you to decide how use it.
Here is the code, you can just copy and paste it to a new playground and test it.
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
var mainContainerView = UIView(frame: CGRect(x: 0, y: 0, width: 140, height: 80))
mainContainerView.backgroundColor = UIColor.white
var containerView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
containerView.backgroundColor = UIColor.white
containerView.layer.cornerRadius = containerView.frame.width / 2
var innerView = UIView(frame: CGRect(x: 10, y: 10, width: 60, height: 60))
innerView.backgroundColor = UIColor.red
innerView.layer.cornerRadius = innerView.frame.width / 2
containerView.addSubview(innerView)
var containerView2 = UIView(frame: CGRect(x: 60, y: 0, width: 80, height: 80))
containerView2.backgroundColor = UIColor.yellow
containerView2.layer.cornerRadius = containerView2.frame.width / 2
var innerView2 = UIView(frame: CGRect(x: 10, y: 10, width: 60, height: 60))
innerView2.backgroundColor = UIColor.green
innerView2.layer.cornerRadius = innerView2.frame.width / 2
containerView2.addSubview(innerView2)
containerView.layer.zPosition = 2
containerView2.layer.zPosition = 1
mainContainerView.addSubview(containerView)
mainContainerView.addSubview(containerView2)
PlaygroundPage.current.liveView = mainContainerView

Using UIDynamics gravity to animate CGRects

My goal is to have a bunch of UIViews that have a property of CGRect falling from offscreen and then piling up. Right now, I have one circle working, but every time I try to add another circle, they won't animate anymore. I am using UIDynamics and its gravity function. Here is my code:
func addBox(location: CGRect) -> UIView {
let newBox = UIView(frame: location)
newBox.backgroundColor = UIColor(red: 186.0/255.0, green: 216.0/255.0, blue: 242.0/255.0, alpha: 1.0)
newBox.layer.cornerRadius = newBox.frame.width/2
hView.insertSubview(newBox, at: 0)
return newBox
}
addBox(location: CGRect(x: 100, y: -100, width: 100, height: 100))
var animator: UIDynamicAnimator? = nil;
let gravity = UIGravityBehavior()
var collision: UICollisionBehavior!
animator = UIDynamicAnimator(referenceView: hView)
func createAnimatorStuff(box: UIView) {
collision = UICollisionBehavior(items: [box])
collision.translatesReferenceBoundsIntoBoundary = true
animator?.addBehavior(collision)
gravity.addItem(box)
gravity.gravityDirection = CGVector(dx: 0, dy: 0.8)
animator?.addBehavior(gravity)
}
func dropDown() {
let xVal = 100
let xVal2 = 700
let box = addBox(location: CGRect(x: xVal, y: 100, width: 100, height: 100))
let box2 = addBox(location: CGRect(x: xVal2, y: 100, width: 100, height: 100))
createAnimatorStuff(box: box)
createAnimatorStuff(box: box2)
}
dropDown()
If anyone could help me and adjust my code to create more circles falling and then piling up I would immensely appreciate it. Truly. Please ask any questions and thank you so much in advance.
(NOTE: the question was updated with these suggestions, so that's why it appears to suggest things that were already done)
The general problem you are having is that you made box a global variable and you keep reinitializing the UIDynamicAnimator. So ...
Remove the box var, then
Change:
func addBox(location: CGRect)
to:
func addBox(location: CGRect) -> UIView
and return newBox at the end.
Then, change:
func createAnimatorStuff() {
to:
func createAnimatorStuff(box: UIView) {
and
addBox(location: CGRect(x: xVal, y: 100, width: 100, height: 100))
createAnimatorStuff()
To
let box = addBox(location: CGRect(x: xVal, y: 100, width: 100, height: 100))
createAnimatorStuff(box: box)
Finally, don't create a new UIDynamicAnimator for every call -- make one and share it. You can do this with
let animator = UIDynamicAnimator(referenceView: hView)
(also, you don't need semicolons in Swift)

Custom progress view

I want to do a custom progress view for my iOS app, with 2 dots. Here is my code:
import UIKit
#IBDesignable
class StepProgressView: UIView {
#IBInspectable var progress: Float = 0
var progressColor = UIColor.blackColor()
var bgColor = UIColor.whiteColor()
override func layoutSubviews() {
self.backgroundColor = UIColor.clearColor()
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
let height = frame.height-8
let circle1 = UIView(frame: CGRect(x: frame.width*(1/3), y: 0, width: frame.height, height: frame.height))
circle1.backgroundColor = bgColor
circle1.layer.cornerRadius = frame.height/2
addSubview(circle1)
let circle2 = UIView(frame: CGRect(x: frame.width*(2/3), y: 0, width: frame.height, height: frame.height))
circle2.backgroundColor = bgColor
circle2.layer.cornerRadius = frame.height/2
addSubview(circle2)
let bgView = UIView(frame: CGRect(x: height/2, y: 4, width: frame.width-height/2, height: height))
bgView.backgroundColor = bgColor
bgView.layer.cornerRadius = height/2
addSubview(bgView)
let progressView = UIView(frame: CGRect(x: 0, y: 4, width: frame.width*CGFloat(progress), height: height))
progressView.backgroundColor = progressColor
progressView.layer.cornerRadius = height/2
addSubview(progressView)
}
}
The result:
However, as you can see, the circles aren't "filled" when the progression pass over one of them, and I don't know how to do that. I could create another view but I don't know how to handle the corner radius.
Can you help me ?
Thanks

Resources