I might be heading totally wrong direction, but what I'm trying to achieve is to load some view first and invoke some methods that will be running afterwards infinitely, while at the same time giving the User the possibility to interact with this view.
My first guess was to use viewDidApper with UIView.animate, as per below;
class MainView: UIView {
...
func animateTheme() {
//want this to print out infinitely
print("Test")
}
}
class ViewController: UIViewController {
#IBOutlet weak var mainView: MainView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.mainView.animateTheme()
}, completion: nil)
}
}
The case is that I can only see a single "Test" printed out but this has to be invoked infinitely. Also, as I mentioned the User needs to have the possibility to interact with the view once everything is loaded.
Thanks a lot for any of your help.
ViewDidAppear will indeed be called latest when the view is loaded, but I don´t think the user can or will have the time to interact before it´s loaded.
If you want to load animations after everything is loaded you could create a timer and wait x seconds before you do that. Below example will be called 10 seconds after initiation.
var timer = Timer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(animateSomething), userInfo: nil, repeats: false)
}
func animateSomething() {
// Animate here
}
Update
To make an infinite loop you can use the following snippet:
UIView.animate(withDuration: 5.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.view.frame = CGRect(x: 100, y: 200, width: 200, height: 200)
}, completion: nil)
I've done something very similar on one of my ViewControllers that loads a UIWebView. While the webpage is loading I show a UILabel with text "Loading" and every 1.5 seconds I add a period to the end of the text. I think the approach you describe above and mine shown below have the same effect and neither would be considered an infinite loop.
private func showLoadingMessage(){
let label = loadingLabel ?? UILabel(frame: CGRect.zero)
label.text = "Loading"
view.addSubview(label)
loadingLabel = label
addEllipsis()
}
private func addEllipsis() {
if webpageHasLoaded { return }
guard let labelText = loadingLabel?.text else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.loadingLabel?.text = labelText + "."
self.addEllipsis()
}
}
Related
I want to add a Progress bar to a tableViewController.
I have one function called HelpersFunctions which do all the calculation.
The function doCalculation is responsible for the calculation.
So, I add the following notification to doCalculation as follow:
NotificationCenter.default.post(name: .return_progress, object: self)
for i in 1...n1 {
//Do all the calculation
}
So, once I reach NotificationCenter.default.post, it will move to a Tableview Controller called CreateNewElementVC
now, inside the ViewDidLoad, I added the following line:
//progress
NotificationCenter.default.addObserver(self, selector: #selector(showProgress), name: .return_progress, object: nil)
In the same swift file, I added the following:
let container_elementProperty: ProgressBarView = {
let view = ProgressBarView()
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
#objc func showProgress() {
if(progressCounter > 1.0){timer.invalidate()}
print("Step 1")
container_elementProperty.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
container_elementProperty.backgroundColor = UIColor(white: 0, alpha: 0.5)
container_elementProperty.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
let queue = DispatchQueue(label: "queue1", qos: .userInteractive)
queue.async {
print("Step 2")
self.view.addSubview(self.container_elementProperty)
}
//view.addSubview(container_elementProperty)
print("Step 3")
container_elementProperty.progress = progressCounter
progressCounter = progressCounter + progressIncrement
let x1: Float = Float(start_Counting)
let x2: Float = Float(End_Counting)
let xx: Float = x1 / x2 * 100
print("Start at: \(xx) %)")
}
So, first I put all the required data in the CreateNewElementVC, then there is a button called run to do all the calculation and then it will move to another TableViewController with all the result.
So while I am inside the function doCalculation, the progress bar should appear .
In fact, the Progress bar container_elementProperty (UIview) appeared just after the calculation is completed which make the progress bar is useless.
Any idea how to make the View called container_elementProperty UIView to be seen ?
I am close to solve this issue as I can see the progress in the stack as below image, I just want to show this on the screen before completing the calculation.
Why I am not able to put the view on the screen while doing the calculation as you can see that step 2 ran first.
The warning related to this issue is: UIView.addSubview(_:) must be used from main thread only.
A Sample Project can be checked on this link at github.
Appreciate any kind of support.
To simulate a progress, such as a network request you cannot simply do a for loop in the main thread, you should use GCD.
To update the progress of an ongoing action use Delegation, not KVO.
Using global variables makes your code flawed, avoid it!
This is a working version of your code.
# Write By Tushar on 11/02/2021#
import UIKit
class ViewController: UIViewController {
let totalValue:Float = 80.0
var firstNumber:Int? = 0
#IBOutlet weak var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
progressBar.trackTintColor = .white
progressBar.tintColor = .red
// Do any additional setup after loading the view.
progressBar.setProgress(0.0, animated: false)
}
func setProgress(firstValue:Float){
let processValue = firstValue/totalValue
progressBar.setProgress(processValue, animated: true)
}
#IBAction func btnResetClick(_ sender: UIButton) {
print("Reset Button Clicked")
firstNumber = 0
progressBar.setProgress(0.0, animated: false)
}
#IBAction func btnShowProgressClick(_ sender: UIButton) {
guard let presentValue = firstNumber else { return }
let newValue = presentValue + 1
firstNumber = newValue
setProgress(firstValue: Float(firstNumber!))
}
}
I am trying get this code to produce multiple "flashes" to notify the user at the end of a countdown. Right now its working great for a single flash but I ideally would like three. Ive tried duplicating the code and putting the function in a loop to no avail.
Thanks in advance for the help!
//
Flash Function
func flash() {
if let wnd = self.view{
var v = UIView(frame: wnd.bounds)
v.backgroundColor = UIColor.black
v.alpha = 1
wnd.addSubview(v)
UIView.animate(withDuration: 1.618, animations: {
v.alpha = 0.0
}, completion: {(finished:Bool) in
print("inside")
v.removeFromSuperview()
})
}
}
//
Use of flash function
if (heatUpSeconds == 0) {
heatUpTimer.invalidate()
// testing screen flashing ideas here cameraView.frame
flash()
var timer:Timer!
timer = Timer.scheduledTimer(timeInterval: delayBetweenFlashes, target: self, selector: #selector(self.flash), userInfo: nil, repeats: true)
I'm using iCarousel in Swift 3 in Xcode. I'm trying to make it work like a sliding banner. I have this function:
func animate() {
DispatchQueue.global(qos: .userInteractive).async {
self.carousel.scrollToItem(at: 0, animated: false)
while(true){
sleep(3)
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}
}
}
I call it in viewDidLoad. It works but I think it's not as good as it could be. After I switch to another view, scrolling goes on and on. What would be the best approach to make it scroll only, when I'm seeing the view, that the carousel is in?
What solved the problem:
var timer: Timer!
self.timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.handleTimer), userInfo: nil, repeats: true)
func handleTimer(){
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}
I have a button (actually two nearly identical buttons) that unhide with an action in another UIView.
The buttons hide automatically 2 seconds after the panning in the second UIView ends using dispatch_after however I would like to keep the buttons visible if either is tapped while they are visible. Here is the timer property and two methods from the UIButton subclass. I have an #IBAction that calls "justTapped" in the ViewController.
var timer = NSTimer()
func hideAndShrink(){
if !self.hidden && !timer.valid{
self.hidden = true
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
Once either button is tapped, they will not hide.
Is the timer still valid while it calls the method in the selector?
The answer to this, thanks to luk2302, is Yes, the timer is valid when it sends the selector
How can I get around this?
As was suggested, I wrote a second method for the timer that hid the buttons without !timer.valid ?
It turned out that I did not need the second method at all as I just had any attempt to hide the buttons call justTapped instead. Here is the final code with expand and shrink animations for both buttons. self.direction is the direction I want the button to expand, from the bottom up, or the top down, and also determines the background image. I think it could have been an enumeration but I haven't figured those out yet.
func unhideAndExpand(){
if self.hidden{
expand(self.frame.size.height)
}
}
func hideAndShrink(){
if !self.hidden && !shrinking{
shrink(self.frame.size.height)
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
func expand(height: CGFloat){
if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
self.hidden = false
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{self.transform = CGAffineTransformIdentity}, completion: nil)
}
func shrink(height: CGFloat){
self.shrinking = true
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
}, completion: {_ in
self.transform = CGAffineTransformIdentity
self.hidden = true
self.shrinking = false})
}
Thanks everyone for the help.
Is the timer still valid while it calls the method in the selector?
Yes, it is
The following is a complete runnable playground example demonstrating that:
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class Bla {
var timer : NSTimer?
init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "check", userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(1.1, target: self, selector: "checkAgain", userInfo: nil, repeats: false)
}
#objc func check(){
print(timer!.valid)
}
#objc func checkAgain(){
check()
}
}
let bla = Bla()
This will first output true and then output false. The timer is still valid when firing. As soon as you leave the fired method the timer gets invalidated.
That will render your if useless since the method will do nothing when fired by the timer because anything && !true evaluates to false.
What you can do to circumvent that issue is create a different method timerFired which sets some internal property of your class, e.g. var timerHasFired = false to true. Your `` then has to check that variable instead of the timer:
var timer = NSTimer()
var timerHasFired = false
func hideAndShrink(){
if !self.hidden && hideAndShrink {
self.hidden = true
}
}
func timerFired() {
timerHasFired = true
hideAndShrink()
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "timerFired", userInfo: nil, repeats: false)
}
Are you sure you need !timer.valid check? If hideAndShrink is called only by timer it seems you can change it to
func hideAndShrink(){
self.hidden = true
}
hideAndShrink will be called only if timer is not invalidated so you can skip this check.
I have a simple gestureRecognizer. When the gesture is "UP" then the menu slides up. "DOWN": The menu slides down. This works.
I have a timer too. It simply counts a number++ every second. This works, too. But together the timer is crashing my GUI.
The animation starts correctly, but when the timer calls my counter-method it's jumping back to the origin GUI.
What kind of problem is that? I found nothing at google, but I think I searched the wrong words. (English isn't my mothertongue and in german there isn't so much literature...)
Here are fragments of my code:
class ViewController: UIViewController, MKMapViewDelegate{
#IBOutlet weak var swipe: UILabel!
var number: Int = 0
#IBOutlet weak var menuView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var topSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
var downSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
topSwipe.direction = .Up
downSwipe.direction = .Down
view.addGestureRecognizer(topSwipe)
view.addGestureRecognizer(downSwipe)
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "counter", userInfo: nil, repeats: true)
}
func counter() {
number++
swipe.text = "\(number)"
}
func handleSwipes(sender:UISwipeGestureRecognizer) {
if (sender.direction == .Up) {
print("TOP")
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y - 100.0)
UIView.animateWithDuration(0.65, animations: {
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
if (sender.direction == .Down) {
menuChoose = true
UIView.animateWithDuration(0.65, animations: {
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y + 100.0)
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
}
}
I tried to lock the timer. So when the gesture begins, then just count the number without swipe.text = "(number)"
The animation goes to end, but after an "unlock" the GUI jumped back to the origin.
Thank u for some codesnippets or keywords that I can search :)
It seems that whenever a UILabel's text property is set, it redraws the view, resulting in resetting menuView's position (perhaps someone can elaborate on the "why" in a comment). If you use auto layout constraints to manipulate the position of menuView, it works. You can do this by setting spaceConstraint.constant += 100 or spaceConstraint.constant -= 100 and then calling
UIView.animateWithDuration(0.65, animations: {
self.view.layoutIfNeeded()
})