So I know swift has mapViewDidFinishRenderingMap(_:fullyRendered:), but I have no clue how I can use that function to only add my mapView onto the view hierarchy when it finishes rendering.
Right now, even with the DispatchGroup .enter() and .leave(), the view.addSubview(mapView) is getting called before it finishes rendering, which results in my MKPolylines not showing up on the map when it loads.
How can I make sure that the map finishes rendering before view.addSubview(mapView) is called?
#Koen basically what my viedDidLoad() is doing:
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView()
let leftMargin:CGFloat = 0
let topMargin:CGFloat = 0
let mapWidth:CGFloat = view.frame.size.width
let mapHeight:CGFloat = view.frame.size.height
mapView?.frame = CGRect(x: leftMargin, y: topMargin, width: mapWidth, height: mapHeight)
mapView?.mapType = MKMapType.standard
mapView?.isZoomEnabled = true
mapView?.isScrollEnabled = true
mapView?.delegate = self
mapView?.showsScale = true
// I have this to make sure they run in order only after the previous block is finished because of dependencies
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
// gets the routes as polylines and adds to mapView
self.fetchRoutes()
group.leave()
}
goup.notify(queue: .main) {
group.enter()
DispatchQueue.main.async {
// depending on the route data, adds the stops as annotations to mapView
self.fetchAnnotations()
group.leave()
}
group.notify(queue: .main) {
// the idea was that once both route and stops are added to the mapView, display mapView to screen
self.view.addSubview(mapView)
}
}
}
Issue was solved by moving all of my data fetching code into a single function and using DispatchGroup to only call addSubview(mapView) after the fetching finishes.
Was not able to find out the root cause, but the issue was solved.
Related
For the life of me I can't get the GIF to display using the SwiftyGif library. Is there something I'm missing here?
var outgoingMessageView: UIImageView!
outgoingMessageView = UIImageView(frame:
CGRect(x: llamaView.frame.maxX - 50,
y: llamaView.frame.minY + 75,
width: bubbleImageSize.width,
height: bubbleImageSize.height))
outgoingMessageView.delegate = self
if textIsValidURL == true {
print("URL is valid")
outgoingMessageView.image = bubbleImage
let maskView = UIImageView(image: bubbleImage)
maskView.frame = outgoingMessageView.bounds
outgoingMessageView.mask = maskView
outgoingMessageView.frame.origin.y = llamaView.frame.minY - 25
let url = URL(string: text)
outgoingMessageView.setGifFromURL(url, manager: .defaultManager, loopCount: -1, showLoader: true)
} else {
outgoingMessageView.image = bubbleImage
}
// Set the animations
label.animation = "zoomIn"
//outgoingMessageView.animation = "zoomIn"
// Add the Subviews
view.addSubview(outgoingMessageView)
print("outgoingMessageView added")
The delegate lets me know it runs successfully via:
gifDidStart
gifURLDidFinish
Checking outgoingMessageView.isAnimatingGif() tells me it's still running.
Checking outgoingMessageView.isDisplayedInScreen(outgoingMessageView) tells me it's not being displayed
It "finishes" almost immediately, but it's the same in the example project, yet the gif still loops and displays in the project. I've changed loop counts, imageviews, not running via a mask as I intended and instead just a UIImageView, changed the GIF urls, all to no avail. Is this problem related to my view structure?
I am calling this function based on actions in a collectionView.Image Example Here
Using the latest SwiftyGIF version.
I just made a sample about this with the following code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
testSwiftyGif()
}
public func testSwiftyGif() {
let imgPath = "https://github.com/kirualex/SwiftyGif/blob/master/SwiftyGifExample/1.gif?raw=true"
let imgUrl = URL(string: imgPath)!
var outgoingMessageView: UIImageView!
outgoingMessageView = UIImageView(frame:
CGRect(x: llamaView.frame.maxX - 50,
y: llamaView.frame.minY + 75,
width: 200,
height: 200))
outgoingMessageView.setGifFromURL(imgUrl, manager: .defaultManager, loopCount: -1, showLoader: true)
self.view.addSubview(outgoingMessageView)
print("outgoingMessageView added")
}
And it adds the gif as intended:
Aparently the issue is your view structure. The image is being added to the view, but the view is not visible due mask, frame or superview position.
Try to check the view hierarchy using the xCode View Hierarchy Debugger
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!))
}
}
Could use some help troubleshooting an issue with a SpriteKit scene.
I have a scene that displays some coins in the main section of the app.
When I present a viewcontroller from the bottom I have no issue. Same for tab bar navigation, no issues.
Here is the view as it should always be displayed.
The issue comes only when I present a viewcontroller from the side.
When the new viewcontroller is dismissed, the scene works, but is distorted.
this is how it is displayed after a viewcontroller is displayed modally and later on dismissed.
EDIT: I forgot to mention that if I swipe vertically on the distorted scene, the distortion is fixed and all is good.
Here is some of the code in viewDidAppear of the viewcontroller.
Thanks for the help.
EDIT 2:
I just tested the app on a iPhone 5 using iOS 10 and the issue doesn't happen. Any chance this might be iOS 11 related?
func configureScene(_ completion: () -> Void) {
defer { completion() }
guard wScene == nil else { return }
let skView = SKView(frame: self.view.frame)
skView.isUserInteractionEnabled = false
skView.backgroundColor = .clear
wScene = WScene(size: view.frame.size)
wScene.backgroundColor = .clear
skView.presentScene(wScene)
view.insertSubview(skView, belowSubview: collectionView)
if let buttonsObstacle = doubleButton?.buttonsView {
let obstacleSize = CGSize.init(width: buttonsObstacle.frame.width, height: buttonsObstacle.frame.height)
obstacle = SKSpriteNode.init(color: .clear, size: obstacleSize)
guard let obstacle = obstacle else { return }
obstacle.name = WScene.obstacleNodeName
let convertedOrigin = view.convert(buttonsObstacle.center, from: buttonsObstacle.superview)
let skConvertedOrigin = skView.convert(convertedOrigin, to: wScene)
obstacle.position = skConvertedOrigin
obstacle.physicsBody = SKPhysicsBody(rectangleOf: obstacleSize)
obstacle.physicsBody?.allowsRotation = false
obstacle.physicsBody?.isDynamic = false
source.scrollHandler = { [weak self] (scrollView) in
guard let strongSelf = self else { return }
strongSelf.buttonsMoved(inView: skView, withScroll: scrollView)
}
wScene.addChild(obstacle)
presenter.loadData()
}
}
I solved my issue.
It was related to the new iOS 11 adjustedContentInset property.
My Coin SK scene was being moved by the scroll handler when the view appeared after a modal transition.
My solution is to disable the scrolling for the first 0.1 second after the view appears. In this way iOS 11 doesn't touch the coins anymore while users are able to scroll correctly because they interact with the view most of the time after at least 0.1 seconds.
I'm facing an issue when trying to periodically animate my nodes on an ARSession. I'm fetching data from Internet every 5 seconds and then with that data I update this nodes (shrink or enlarge).
My code looks something like this:
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
fetchDataFromServer() {
let fetchedData = $0
DispatchQueue.main.async {
node1.update(fetchedData)
node2.update(fetchedData)
node3.update(fetchedData)
}
if stopCondition { timer.invalidate() }
}
}
Problem is that when calling the updates I'm seeing a glitch in which the camera seems to freeze for a fraction of second and I see the following message in the console: [Technique] World tracking performance is being affected by resource constraints [1]
Update happens correctly, but the UX is really clumpsy if every 5 seconds I get these "short freezes"
I've tried creating a concurrent queue too:
let animationQueue = DispatchQueue(label: "animationQueue", attributes: DispatchQueue.Attributes.concurrent)
and call animationQueue.async instead of main queue but problem persists.
I'd appreciate any suggestions.
EDIT: Each of the subnodes on it's update method looks like this
private func growingGeometryAnimation(newHeight height: Float) -> CAAnimation{
// Change height
let grow = CABasicAnimation(keyPath: "geometry.height")
grow.toValue = height
grow.fromValue = prevValue
// .... and the position
let move = CABasicAnimation(keyPath: "position.y")
let newPosition = getNewPosition(height: height)
move.toValue = newPosition.y + (yOffset ?? 0)
let growGroup = CAAnimationGroup()
growGroup.animations = [grow, move]
growGroup.duration = 0.5
growGroup.beginTime = CACurrentMediaTime()
growGroup.timingFunction = CAMediaTimingFunction(
name: kCAMediaTimingFunctionEaseInEaseOut)
growGroup.fillMode = kCAFillModeForwards
growGroup.isRemovedOnCompletion = false
growGroup.delegate = self
return growGroup
}
self.addAnimation(growingGeometryAnimation(newHeight: self.value), forKey: "bar_grow_animation")
To make any updates to the scene use SCNTransaction, it makes sure all of the changes are made on the appropriate thread.
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
fetchDataFromServer() {
let fetchedData = $0
SCNTransaction.begin()
node1.update(fetchedData)
node2.update(fetchedData)
node3.update(fetchedData)
SCNTransaction.commit()
if stopCondition { timer.invalidate() }
}
}
I am trying to show an activity indicator while creating a csv file, but it does not show. I am guessing I should use dispatch_async somehow, but I cant figure out how to do this in swift 3.
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
override func viewDidLoad() {
super.viewDidLoad()
// activity indicator
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 100 ,y: 200,width: 50,height: 50)) as UIActivityIndicatorView
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func writeToCsv() {
self.activityIndicator.startAnimating() // start the animation
let fileName = "events.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
var csvText = self.name! + "\n"
csvText += "Date,Start time,End time\n"
// create rest of comma-separated string
for event in self.events! {
let newLine = "\(event.date),\(event.startTime),\(event.endTime)\n"
csvText.append(newLine)
}
// write to csv
do {
try csvText.write(to: path!, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print(error)
}
// create and present view controller with send options
let vc = UIActivityViewController(activityItems: [path as Any], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
self.activityIndicator.stopAnimating() // stop the animation
}
Err, alright bit hard to answer this without a bit more context about your view setup. First of all, make sure your activity indicator is visible without calling the writeCsv method, so you know your view hierarchy is correct. ( I.E. It could be that it is hidden behind some other subview )
Next, in Swift3 Dispatch has been changed to a newer API. I'm not sure whether on OSX they use the raw libdispatch Swift wrapper, but in any case you access it like this:
Background default queue:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { /* code */ }
Main thread:
DispatchQueue.main.async { /* Mainthread code ( UIKit stuff ) */ }
Your own custom queue for CSV generation blocks:
let queue = DispatchQueue(label: "csvgenerator.queue")
queue.async { /* code */ }
Now for your animating / stopAnimation, make sure you call your UIKit related code from the mainthread to prevent weird glitechs and or crashes
Namely:
DispatchQueue.main.async {
self.activityIndicator?.startAnimating()
}
Another good idea might be to use NSOperationQueue instead. It internally uses GCD I believe, but it does integrate very well into iOS and might make some of the dispatching a lot easier to implement. I myself always use GCD instead, but I have never really had long queeu's of work that needed to be done. One of the advantages of NSOperationQueue is that it is a lot more user friendly in terms of cancelling dispatch blocks.
An interesting session video about NSOperationQueue in the WWDC app written by Dave Delong: WWDC Videos 2015
A small minor changes I'd make to your writeCSV method:
guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) else {
// Should throw an error here, or whatever is handy for your app
return
}
Try to avoid forced unwrapping at all stages where possible.
In methods that have this, you can for instance add "throws" to the end of the function definition so you can use try without the do and catch block, while also being able to throw errors in your guard statement so whatever calls writeCsv can catch the error and more easily display it to the user.