In my case, you can star and change stop shimmer animation? - swift

I have a case of such a want to do so after refresh a couple of seconds were loaded with shimmer and finished animation.
my code starAnimatiom :
func startAnimation() {
for animateView in getSubViewsForAnimate() {
animateView.clipsToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.withAlphaComponent(0.8).cgColor, UIColor.clear.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.7, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.8)
gradientLayer.frame = animateView.bounds
animateView.layer.mask = gradientLayer
let animation = CABasicAnimation(keyPath: "transform.translation.x")
animation.duration = 1.5
animation.fromValue = -animateView.frame.size.width
animation.toValue = animateView.frame.size.width
animation.repeatCount = .infinity
gradientLayer.add(animation, forKey: "")
}
}
func getSubViewsForAnimate() -> [UIView] {
var obj: [UIView] = []
for objView in view.subviewsRecursive() {
obj.append(objView)
}
return obj.filter({ (obj) -> Bool in
obj.shimmerAnimation
})
}
My code function stopAnimation;
#objc func stopAnimation() {
for animateView in getSubViewsForAnimate() {
animateView.layer.removeAllAnimations()
animateView.layer.mask = nil
timerShimmer.invalidate()
refresh.endRefreshing()
}
}
When I pull down and do the update the animation continues to act and for some reason does not stop.What did I do wrong?
#objc func obnova() {
self.startAnimation()
self.tableView.reloadData()
self.loadObjects1()
self.loadObjects2()
self.loadObjects3()
// self.refresh.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
timerShimmer = Timer.init(timeInterval: 0.2, target: self, selector: #selector(stopAnimation), userInfo: nil, repeats: true)
}
Help me Please?

Change the last section like below and try
func startTimer() {
if timerShimmer != nil {
timerShimmer.invalidate()
timerShimmer = nil
}
timerShimmer = Timer.init(timeInterval: 0.2, target: self, selector: #selector(stopAnimation), userInfo: nil, repeats: true)
}
#objc func obnova() {
self.startAnimation()
self.tableView.reloadData()
self.loadObjects1()
self.loadObjects2()
self.loadObjects3()
startTimer()
}
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}

Related

Processing touches on moving/ animating UiViews

I currently have the problem that touches are not always identified correctly,
My goal is to have 3 gestures,The 3 gestures are
A user can tap on a view and the tap gets recognised,
A user can double tap on a view and the double tap is recognised,
A user can move their finger on the screen and if a view is below it
a tab is recognised.
However I have multiple views all animating constantly and they may overlap,
Currently I sort views by size and have the smallest views on top of larger views.
And I typically get an issue that UIViews are not recognised when tapping on them. In particular double taps, swiping seems to work fine most of the time however the whole experience is very inconsistent.
The current code I'm using to solve the problem is:
class FinderrBoxView: UIView {
private var lastBox: String?
private var throttleDelay = 0.01
private var processQueue = DispatchQueue(label: "com.finderr.FinderrBoxView")
public var predictedObjects: [FinderrItem] = [] {
didSet {
predictedObjects.forEach { self.checkIfBoxIntersectCentre(prediction: $0) }
drawBoxs(with: FinderrBoxView.sortBoxByeSize(predictedObjects))
setNeedsDisplay()
}
}
func drawBoxs(with predictions: [FinderrItem]) {
var newBoxes = Set(predictions)
var views = subviews.compactMap { $0 as? BoxView }
views = views.filter { view in
guard let closest = newBoxes.sorted(by: { x, y in
let xd = FinderrBoxView.distanceBetweenBoxes(view.frame, x.box)
let yd = FinderrBoxView.distanceBetweenBoxes(view.frame, y.box)
return xd < yd
}).first else { return false }
if FinderrBoxView.updateOrCreateNewBox(view.frame, closest.box)
{
newBoxes.remove(closest)
UIView.animate(withDuration: self.throttleDelay, delay: 0, options: .curveLinear, animations: {
view.frame = closest.box
}, completion: nil)
return false
} else {
return true
}
}
views.forEach { $0.removeFromSuperview() }
newBoxes.forEach { self.createLabelAndBox(prediction: $0) }
accessibilityElements = subviews
}
func update(with predictions: [FinderrItem]) {
var newBoxes = Set(predictions)
var viewsToRemove = [UIView]()
for view in subviews {
var shouldRemoveView = true
for box in predictions {
if FinderrBoxView.updateOrCreateNewBox(view.frame, box.box)
{
UIView.animate(withDuration: throttleDelay, delay: 0, options: .curveLinear, animations: {
view.frame = box.box
}, completion: nil)
shouldRemoveView = false
newBoxes.remove(box)
}
}
if shouldRemoveView {
viewsToRemove.append(view)
}
}
viewsToRemove.forEach { $0.removeFromSuperview() }
for prediction in newBoxes {
createLabelAndBox(prediction: prediction)
}
accessibilityElements = subviews
}
func checkIfBoxIntersectCentre(prediction: FinderrItem) {
let centreX = center.x
let centreY = center.y
let maxX = prediction.box.maxX
let minX = prediction.box.midX
let maxY = prediction.box.maxY
let minY = prediction.box.minY
if centreX >= minX, centreX <= maxX, centreY >= minY, centreY <= maxY {
// NotificationCenter.default.post(name: .centreIntersectsWithBox, object: prediction.name)
}
}
func removeAllSubviews() {
UIView.animate(withDuration: throttleDelay, delay: 0, options: .curveLinear) {
for i in self.subviews {
i.frame = CGRect(x: i.frame.midX, y: i.frame.midY, width: 0, height: 0)
}
} completion: { _ in
self.subviews.forEach { $0.removeFromSuperview() }
}
}
static func getDistanceFromCloseBbox(touchAt p1: CGPoint, items: [FinderrItem]) -> Float {
var boxCenters = [Float]()
for i in items {
let distance = Float(sqrt(pow(i.box.midX - p1.x, 2) + pow(i.box.midY - p1.y, 2)))
boxCenters.append(distance)
}
boxCenters = boxCenters.sorted { $0 < $1 }
return boxCenters.first ?? 0.0
}
static func sortBoxByeSize(_ items: [FinderrItem]) -> [FinderrItem] {
return items.sorted { i, j -> Bool in
let iC = sqrt(pow(i.box.height, 2) + pow(i.box.width, 2))
let jC = sqrt(pow(j.box.height, 2) + pow(j.box.width, 2))
return iC > jC
}
}
static func updateOrCreateNewBox(_ box1: CGRect, _ box2: CGRect) -> Bool {
let distance = sqrt(pow(box1.midX - box2.midX, 2) + pow(box1.midY - box2.midY, 2))
print(distance)
return distance < 50
}
static func distanceBetweenBoxes(_ box1: CGRect, _ box2: CGRect) -> Float {
return Float(sqrt(pow(box1.midX - box2.midX, 2) + pow(box1.midY - box2.midY, 2)))
}
func createLabelAndBox(prediction: FinderrItem) {
let bgRect = prediction.box
let boxView = BoxView(frame: bgRect ,itemName: "box")
addSubview(boxView)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
// handling code
// NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
processTouches(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
processTouches(touches, with: event)
}
func processTouches(_ touches: Set<UITouch>, with event: UIEvent?) {
if UIAccessibility.isVoiceOverRunning { return }
if predictedObjects.count == 0 { return }
if let touch = touches.first {
let hitView = hitTest(touch.location(in: self), with: event)
if hitView?.accessibilityLabel == lastBox { return }
lastBox = hitView?.accessibilityLabel
guard let boxView = hitView as? BoxView else {
return
}
UIView.animate(withDuration: 0.1, delay: 0, options: .curveLinear) {
boxView.backgroundColor = UIColor.yellow.withAlphaComponent(0.5)
} completion: { _ in
UIView.animate(withDuration: 0.1, delay: 0, options: .curveLinear, animations: {
boxView.backgroundColor = UIColor.clear
}, completion: nil)
}
}
}
}
class BoxView: UIView {
let id = UUID()
var itemName: String
init(frame: CGRect, itemName: String) {
self.itemName = itemName
super.init(frame: frame)
if !UIAccessibility.isVoiceOverRunning {
let singleDoubleTapRecognizer = SingleDoubleTapGestureRecognizer(
target: self,
singleAction: #selector(handleDoubleTapGesture),
doubleAction: #selector(handleDoubleTapGesture)
)
addGestureRecognizer(singleDoubleTapRecognizer)
}
}
#objc func navigateAction() -> Bool {
// NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
return true
}
required init?(coder aDecoder: NSCoder) {
itemName = "error aDecoder"
super.init(coder: aDecoder)
}
#objc func handleDoubleTapGesture(_: UITapGestureRecognizer) {
// handling code
// NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
}
}
public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
public var timeout: TimeInterval = 0.5 {
didSet {
targetDelegate.timeout = timeout
}
}
public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
super.init(target: targetDelegate, action: #selector(targetDelegate.recognizerAction(recognizer:)))
}
}
class SingleDoubleTapGestureRecognizerDelegate: NSObject {
weak var target: AnyObject?
var singleAction: Selector
var doubleAction: Selector
var timeout: TimeInterval = 0.5
var tapCount = 0
var workItem: DispatchWorkItem?
init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
self.target = target
self.singleAction = singleAction
self.doubleAction = doubleAction
}
#objc func recognizerAction(recognizer: UITapGestureRecognizer) {
tapCount += 1
if tapCount == 1 {
workItem = DispatchWorkItem { [weak self] in
guard let weakSelf = self else { return }
weakSelf.target?.performSelector(onMainThread: weakSelf.singleAction, with: recognizer, waitUntilDone: false)
weakSelf.tapCount = 0
}
DispatchQueue.main.asyncAfter(
deadline: .now() + timeout,
execute: workItem!
)
} else {
workItem?.cancel()
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else { return }
weakSelf.target?.performSelector(onMainThread: weakSelf.doubleAction, with: recognizer, waitUntilDone: false)
weakSelf.tapCount = 0
}
}
}
}
class FinderrItem: Equatable, Hashable {
var box: CGRect
init(
box: CGRect)
{
self.box = box
}
func hash(into hasher: inout Hasher) {
hasher.combine(Float(box.origin.x))
hasher.combine(Float(box.origin.y))
hasher.combine(Float(box.width))
hasher.combine(Float(box.height))
hasher.combine(Float(box.minX))
hasher.combine(Float(box.maxY))
}
static func == (lhs: FinderrItem, rhs: FinderrItem) -> Bool {
return lhs.box == rhs.box
}
}
By default view objects block user interaction while an animation is "in flight". You need to use one of the "long form" animation methods, and pass in the option .allowUserInteraction. Something like this:
UIView.animate(withDuration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
myView.alpha = 0.5
})

Massive CPU overuse whenever I push ViewController - Swift - Programmatically

I have a high CPU level whenever I push a new ViewController from the navigationController.
I used the Time Profiler tool and it turns out that the issue that is causing this CPU overuse is related to a ViewController I embedded in the rootViewController.
The ViewController I'm talking about is as follows:
class QuoteGeneratorController : UIViewController {
let interactiveShowTextView : UITextView = {
let textView = UITextView()
textView.font = UIFont(name: "avenir-black", size: 35)
textView.textColor = .white
textView.textAlignment = .left
textView.textContainer.maximumNumberOfLines = 3
textView.backgroundColor = .clear
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
return textView
}()
let progressBar : UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.trackTintColor = UIColor.clear
progressView.tintColor = UIColor.white
return progressView
}()
let quotesArray = ["Hello", "Hello2", "Hello3"]
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("I'm called")
view.subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
}
override func viewDidAppear(_ animated: Bool) {
setUpAnimation()
}
override func viewDidLoad() {
setUpUI()
setUpAnimation()
}
fileprivate func setUpUI(){
view.addSubview(interactiveShowTextView)
view.addSubview(progressBar)
interactiveShowTextView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
interactiveShowTextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
progressBar.anchor(top: interactiveShowTextView.bottomAnchor, leading: interactiveShowTextView.leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 10, left: 0, bottom: 0, right: 0), size: .init(width: 70, height: 9))
}
var iterator : Int = 0
fileprivate func setUpAnimation(){
switch iterator {
case 0:
animateAndIterate()
case 1:
animateAndIterate()
case 2:
animateAndIterate()
default:
self.iterator = 0
self.setUpAnimation()
}
}
fileprivate func animateAndIterate(){
UIView.animate(withDuration: 0.0, animations: {
self.progressBar.layoutIfNeeded()
}, completion: { finished in
self.progressBar.progress = 1.0
self.interactiveShowTextView.text = self.quotesArray[self.iterator]
self.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3, delay: 0.0, options: [.curveLinear], animations: {
self.progressBar.layoutIfNeeded()
self.perform(#selector(self.afterAnimation), with: nil, afterDelay: 2.5)
}, completion: { finished in
self.interactiveShowTextView.fadeOut()
self.progressBar.progress = 0
self.iterator = self.iterator + 1
self.setUpAnimation()
})
})
}
#objc func afterAnimation() {
self.interactiveShowTextView.fadeOut()
}
}
I actually don't know what could have caused the issue and since I'm new to time profiler I though that some of you have encountered the same process through development.
In Time Profiler I'm getting this indications:
When using completion blocks it's recommended to use weak references to the result. Otherwise the ARC will count every reference you used inside those block.
Use closures like this:
view.subviews.forEach { [weak self] subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
// (...)
fileprivate func animateAndIterate() {
UIView.animate(withDuration: 0.0,
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
}, completion: { [weak self] finished in
self?.progressBar.progress = 1.0
self?.interactiveShowTextView.text = self.quotesArray[self.iterator]
self?.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3,
delay: 0.0,
options: [.curveLinear],
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
self?.perform(#selector(self.afterAnimation),
with: nil, afterDelay: 2.5)
}, completion: { finished in
self?.interactiveShowTextView.fadeOut()
self?.progressBar.progress = 0
self?.iterator = self.iterator + 1
self?.setUpAnimation()
})
})
}

MKAnnotationView image toggling's transition issue

I have 2 image states for the MKAnnotationView, which are selected and deselected. The issue is that the transition between these two states is poor. There isn't much online about this and I'm having trouble with transitions, generally.
Here is the MKAnnotationView I am using:
class CustomPinView: MKAnnotationView {
func updateImage() {
guard let mapAnnotation = annotation as? MapAnnotation else {return}
if let selectedImageName = mapAnnotation.selectedImageName, isSelected {
image = UIImage(inCurrentBundleWithName: selectedImageName)
} else if let imageName = mapAnnotation.imageName {
image = UIImage(inCurrentBundleWithName: imageName)
} else {
image = nil
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
updateImage()
}
override var annotation: MKAnnotation? {
didSet {
updateImage()
}
}
}
I updated the updateImage function to look like this:
private func updateImage() {
CATransaction.begin()
CATransaction.setAnimationDuration(0.2)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeOut))
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
guard let mapAnnotation = self.annotation as? MapAnnotation else {return}
if let selectedImageName = mapAnnotation.selectedImageName, self.isSelected {
self.image = UIImage(inCurrentBundleWithName: selectedImageName)
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.6)
} else if let imageName = mapAnnotation.imageName {
self.image = UIImage(inCurrentBundleWithName: imageName)
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
} else {
self.image = nil
}
self.centerOffset = CGPoint(x: 0.5, y: 0.5)
}, completion: nil)
CATransaction.commit()
}
This is extracted from this answer here: https://stackoverflow.com/a/54431257/4114335

Swift animated circular progress bar

I have created a circular progress bar in Swift that animated over 1.5 seconds to value 1 when user hold on view. But I want to add a new viewcontroller when animation is done and restart my circular progressbar if user ended to early. Can someone help me?
Circulars progress bar is working with animation when user hold on view and stop at release.
class CounterView: UIView {
var bgPath: UIBezierPath!
var shapeLayer: CAShapeLayer!
var progressLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
bgPath = UIBezierPath()
self.simpleShape()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
bgPath = UIBezierPath()
self.simpleShape()
}
func simpleShape()
{
createCirclePath()
shapeLayer = CAShapeLayer()
shapeLayer.path = bgPath.cgPath
shapeLayer.lineWidth = 5
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.clear.cgColor
progressLayer = CAShapeLayer()
progressLayer.path = bgPath.cgPath
progressLayer.lineCap = kCALineCapRound
progressLayer.lineWidth = 5
progressLayer.fillColor = nil
progressLayer.strokeColor = UIColor.yellow.cgColor
progressLayer.strokeEnd = 0.0
self.layer.addSublayer(shapeLayer)
self.layer.addSublayer(progressLayer)
}
private func createCirclePath()
{
let x = self.frame.width/2
let y = self.frame.height/2
let center = CGPoint(x: x, y: y)
print(x,y,center)
bgPath.addArc(withCenter: center, radius: x/CGFloat(2), startAngle: CGFloat(0), endAngle: CGFloat(6.28), clockwise: true)
bgPath.close()
}
var animationCompletedCallback: ((_ isAnimationCompleted: Bool) -> Void)?
func setProgressWithAnimation(duration: TimeInterval, value: Float) {
CATransaction.setCompletionBlock {
if let callBack = self.animationCompletedCallback { callBack(true) }
}
CATransaction.begin()
let animation = CABasicAnimation (keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = value
animation.repeatCount = 1
animation.timingFunction = CAMediaTimingFunction (name: kCAMediaTimingFunctionLinear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateprogress")
CATransaction.commit()
}
func removeLayers() {
shapeLayer.removeAllAnimations()
shapeLayer.removeFromSuperlayer()
progressLayer.removeAllAnimations()
progressLayer.removeFromSuperlayer()
}
}
class ViewController: UIViewController {
#IBOutlet weak var counterView: CounterView!
#IBOutlet weak var holdView: UIView!
var isAnimationCompleted = false
override func viewDidLoad() {
super.viewDidLoad()
addLongPressGesture()
addCounterViewCallback()
}
#objc func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
// self.counterView.simpleShape()
self.counterView.setProgressWithAnimation(duration: 1.5, value: 1.0)
}
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
self.counterView.removeLayers()
}
}
}
func addLongPressGesture(){
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gesture:)))
lpgr.minimumPressDuration = 0
self.holdView.addGestureRecognizer(lpgr)
}
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
weakSelf.addFlashView()
}
}
func addFlashView(){
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "ResultView") as! Flash
self.present(resultViewController, animated:true, completion:nil)
}
Add new viewcontroller when animation is done and restart animation if user release view and hold on it again.
Add a callback to know when animation is ended. And use CATransaction to know when animation is completed.
var animationCompletedCallback: (() -> Void)?
func setProgressWithAnimation(duration: TimeInterval, value: Float) {
CATransaction.setCompletionBlock {
if let callBack = animationCompletedCallback {
callBack()
}
}
CATransaction.begin()
let animation = CABasicAnimation (keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = value
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction (name: kCAMediaTimingFunctionLinear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateprogress")
CATransaction.commit()
}
And add this function after addLongPressGesture() in viewDidLoad() :
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] in
guard let weakSelf = self else {return}
weakSelf.addFlashView()
}
}
To remove layer use this:
func removeLayers() {
shapeLayer.removeAllAnimations()
shapeLayer.removeFromSuperlayer()
progressLayer.removeAllAnimations()
progressLayer.removeFromSuperlayer()
}
Update 1:
To remove animation if user stops pressing, you need can add on variable in callback like this :
var animationCompletedCallback: ((isAnimationCompleted: Bool) -> Void)?
So now callback in CounterView will be :
if let callBack = animationCompletedCallback { callBack(true) }
In your controller add one variable:
var isAnimationCompleted = false
Change addCounterViewCallback() :
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
weakSelf.addFlashView()
}
}
Now you can add one condition in your longPress():
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
//Call remove layers code
}
}
Update 2:
Add a variable in CounterView:
var isAnimationCompleted = true
Change callback like this :
CATransaction.setCompletionBlock {
if let callBack = self.animationCompletedCallback { callBack(isAnimationCompleted) }
}
In controller longPress() :
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
self.counterView.isAnimationCompleted = false
self.counterView.removeLayers()
}
}
Modify addCounterViewCallback() to this:
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
if isCompleted {
weakSelf.addFlashView()
}
}
}

Display a 3D object forever in ARkit

I am trying to recognise an object to displaying 3D model,
and it’s work when I put the camera on the object the 3D model displayed
my problem is 3d model displayed only for around 5 second then it disappear
so how can I display my 3d model forever?
This my code section:
class ViewController: UIViewController, UITextFieldDelegate
{
var proximityObserver: ProximityObserver! // Beacon declaration
#IBOutlet weak var sceneView: ARSCNView!
//#IBOutlet var sceneView: ARSCNView!
let fadeDuration: TimeInterval = 0.3
let rotateDuration: TimeInterval = 3
let waitDuration: TimeInterval = 0.5
lazy var fadeAndSpinAction: SCNAction = {
return .sequence([
.fadeIn(duration: fadeDuration),
.rotateBy(x: 0, y: 0, z: CGFloat.pi * 360 / 180, duration: rotateDuration),
.wait(duration: waitDuration),
.fadeOut(duration: fadeDuration)
])
}()
lazy var fadeAction: SCNAction = {
return .sequence([
.fadeOpacity(by: 0.8, duration: fadeDuration),
.wait(duration: waitDuration),
.fadeOut(duration: fadeDuration)
])
}()
lazy var bookNode: SCNNode = {
guard let scene = SCNScene(named: "art.scnassets/book.scn"),
let node = scene.rootNode.childNode(withName: "book", recursively: true) else { return SCNNode() }
let scaleFactor = 0.1
node.scale = SCNVector3(scaleFactor, scaleFactor, scaleFactor)
return node
}()
override func viewDidLoad() {
super.viewDidLoad()
let estimoteCloudCredentials = CloudCredentials(appID: "reem-badr-s-proximity-for--6o4", appToken: "8be2dff5dc16b9747b7fafe97ff53708")
proximityObserver = ProximityObserver(credentials: estimoteCloudCredentials, onError: { error in
print("ProximityObserver error: \(error)")
})
let zone = ProximityZone(tag: "reem-badr-s-proximity-for--6o4", range: ProximityRange.near)
zone.onEnter = { contexts in
print("enter")
self.sceneView.delegate = self as ARSCNViewDelegate
self.configureLighting()
}
zone.onExit =
{ contexts in
print("Exit")
}
proximityObserver.startObserving([zone])
}
func configureLighting() {
sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
resetTrackingConfiguration()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
#IBAction func resetButtonDidTouch(_ sender: UIBarButtonItem) {
resetTrackingConfiguration()
}
func resetTrackingConfiguration()
{
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else { return }
let configuration = ARWorldTrackingConfiguration()
configuration.detectionImages = referenceImages
sceneView.session.run(configuration)
}
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
{
DispatchQueue.main.async {
guard let imageAnchor = anchor as? ARImageAnchor,
let imageName = imageAnchor.referenceImage.name else { return }
let overlayNode = self.getNode(withImageName: imageName)
overlayNode.opacity = 0
overlayNode.position.y = 0.2
overlayNode.runAction(self.fadeAndSpinAction)
node.addChildNode(overlayNode)
}
func getPlaneNode(withReferenceImage image: ARReferenceImage) -> SCNNode {
let plane = SCNPlane(width: image.physicalSize.width,
height: image.physicalSize.height)
let node = SCNNode(geometry: plane)
return node
}
func getNode(withImageName name: String) -> SCNNode
{
var node = SCNNode()
switch name
{
case "Book":
node = bookNode
default:
break
}
return node
}
}
and so on...