Moving a view in viewDidAppear? - ios

I've got a view that I'm wanting to have in a specific area of the screen based on a value.
I'm trying to keep the view's location the same when a user comes back to the screen after leaving, so I'm passing around a variable of "currentSearch" , and in viewDidAppear I'm checking that value and trying to set the location accordingly.
if currentSearch == "new" {
self.menuBar.center.x = self.new.center.x
} else ...... //checking my other variables and doing relative location moves.
I set a breakpoint and it's going into this code and getting to the center.x bit, but the view isn't moving.
I tried animating it with a duration of 0 or .1, the view didn't move. I also tried something I saw someone else mention that looked like
let f = menuBar.frame
let menubar.center.x = self.new.center.x
let menuBar.frame = f
that also did not work.
Any ideas why the view is just staying at where it's set in the storyboard?

Related

MapBox get visibleCoordinateBounds in a CGRect

I'm working on an iOS app with a MapBox map.
I'm displaying MGLAnnotations but I only want to create the annotations that can be seen on the screen at a current moment.
Problem is : I have a UIView on the bottom of my screen so the bounds I get with mapView.visibleCoordinateBounds function is not really accurate because I can't see the bottom of my map.
I can't just resize my map because the bottom view I talked about doesn't cover the entire map, we still see parts of the map behind the bottom view.
So my question is, how can I get the visibleCoordinateBounds for a CGRect over my MapView ?
My current solution works only for the entire map view bounds and not the bounds of the CGRect area I want to display the annotations :
if ((mapView.visibleCoordinateBounds.sw).latitude...(mapView.visibleCoordinateBounds.ne).latitude ~= latX && (mapView.visibleCoordinateBounds.sw).longitude...(mapView.visibleCoordinateBounds.ne).longitude ~= lngY) {
print("VISIBLE")
}
Thanks
I finally found a way to do it.
I just calculate the percentage of my mapview hidden by the bottom view and I minus this number to the south latitude :
var valueToRemoveBottom = (mapView.visibleCoordinateBounds.ne).latitude - (mapView.visibleCoordinateBounds.sw).latitude
let percentageUsedByBottom = (bottomView.frame.height * 100 / mapView.frame.height) / 100
valueToRemoveBottom = valueToRemoveBottom * Double(percentageUsedByBottom)
if (((mapView.visibleCoordinateBounds.sw).latitude + valueToRemoveBottom)...((mapView.visibleCoordinateBounds.ne).latitude) ~= latX && (mapView.visibleCoordinateBounds.sw).longitude...(mapView.visibleCoordinateBounds.ne).longitude ~= lngY) {
// visible
}

How to clear and redraw contents of a UIView (present inside a view controller) with swift?

I've a UIView inside a View Controller in which I'm drawing few lines as required by my app. After a certain point of time, I want some of those lines to disappear and a few other to appear in the same view. Approach I'm using as of now is that I'm clearing the UIView and redrawing all the lines I want to draw in the updated view.
Can somebody tell me what's the right way to go about it? I've gone through various questions that sound similar but it hasn't helped much. Till now I've tried things like:-
outletView.setNeedsDisplay()
and
let context = UIGraphicsGetCurrentContext()
context?.clear(outletView.frame)
None of these seem to make any difference.
If I call viewDidLoad() again since all the lines are updated now. New lines to be drawn come up but the ones that were supposed to disappear don't go away. Variables for lines are updated correctly since other logic I have which checks line variable's values is working fine after the update is supposed to happen. Only problem is with the redraw part. In fact, if I understand this correctly, problem is only with cleaning the old uiview contents. If cleaning happens properly, redraw with viewDidLoad will show correct lines drawn.
P.S. I know that calling viewDidLoad() explicitly isn't a good practice. Hope to find a solution to this problem without having to call viewDidLoad again.
Maybe you could draw your lines in different layers of the view, delete the layer containing the lines that need to disappear and create a new layer for the new lines. You can draw in layoutSubviews() and use self.setNeedsLayout() when you need to update the view.
remove:
guard let sublayers = yourView.layer.sublayers else { return }
for layer in sublayers {
layer.removeFromSuperlayer()
}
add:
let linesPath = UIBezierPath()
let linesLayer = CAShapeLayer()
linesPath.move(to: CGPoint(x: 0, y: 0)
linesPath.addLine(to: CGPoint(x: 50, y: 100)
lineLayer.path = linesPath.cgPath
linesLayer.lineWidth = 1.0
linesLayer.strokeColor = UIColor.black
yourView.layer.addSublayer(linesLayer)

Use UIPanGestureRecognizer to drag view from one position to limited position

In my application I have an UIView.I want functionality such that user can drag the view from its original position to particular limited position for this I have used **UIPanGestureRecognizer Class ** and in gestureRecognizer.state == .Changed condition I am changing the coordinates of view .I am able to drag the view to limited position when moving slowly but The problem is if the user drags the view very rapidly upward or downward the screen, then the view can be pulled beyond the limits I put on the Y position
if(upperLimit > (self.topbaseConstrant.constant * -1))
{
self.topbaseConstrant.constant += gestureRecognizer.translationInView(self.view!).y
gestureRecognizer.setTranslation(CGPointZero, inView: self.view!)
}
I have been trying to solve the issue since last three days .Please give me suggestion
Thanks in advance
Use the min function to determine upper limits
let newPosition = topbaseConstrant.constant + panGestureRecognizer.translationInView(nil).y
topbaseConstrant.constant = min(upperLimit, newPosition)
If you drag quickly and blow past your constraint, the min function will always return that upper constraint as your new position.

Custom user location marker with custom accuracy circle in MKMapView

I'm implementing simple navigation and to display user location I'm using custom MKAnnotationView:
let reuseId = "userLocationPin"
userLocationViewAnnotation = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if userLocationViewAnnotation == nil {
userLocationViewAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
userLocationViewAnnotation!.canShowCallout = true
userLocationViewAnnotation!.centerOffset = CGPoint(x: 0.9, y: -2)
userLocationViewAnnotation!.image = UIImage(named: "User_location_red_moving_x1")
}
And this code is working fine. Next I need to add accuracy circle. I'm adding it by MKCircle overlay:
userLocationCircle = MKCircle(centerCoordinate: location.coordinate, radius: location.horizontalAccuracy)
map.addOverlay(userLocationCircle!)
The problem is that user location is updating more frequently and with animation by MKMap internally, but circle is updating after user location change (in my code) so it's jumping from one point to another.
Is it possible to add this circle to MKAnnotationView, or maybe do you have any other ideas?
Solution 1 (which I did before)
Do not set MKMapView.showsUserLocation to true. Which means that you cannot use mapView:didUpdateUserLocation: of the delgate any more. That means that fetching the user's location is less convenient. You will have to use Core Location for that. (Tons of tutorials around)
Then you have to set the map's center ans span accordingly but yourself. By doing so you can add any annotation that you want for the user's location. Just respond to mapView(_:viewForAnnotation:) and return the view that you want to be displayed.
Solution 2: (Which I guess works but I never tried it myself)
Just go along with MKMapView.showsUserLocation as you did before but respond to mapView(_:viewForAnnotation:) in any case. Debug it. On the first call to this method it is handed in the standard user annotation. (I just don't know its type/class name out of the top of my head. It is the first one. That's why I ask you to debug for it.)
Just don't return nil but return the view that you want to be displayed.
It is worth a try. It is less work than solution 1 if it works.
However, solution 1 gives you much more flexibilty and full control over the part of the world that is currently displayed in the Map.

CALayer delegate is only called occasionally, when using Swift

I'm new to IOS and Swift, so I've started by porting Apple's Accelerometer example code to Swift.
This was all quite straightforward. Since the Accelerometer API has been deprecated, I used Core Motion instead, and it works just fine. I also switched to a storyboard.
The problem I have is that my layer delegate is only rarely called. It will go for a few minutes and never get called, and then it will get called 40 times a second, and then go back to not being called. If I context switch, the delegate will get called, and one of the sublayers will be displayed, but there are 32 sublayers, and I've yet to see them all get drawn. What's drawn seems to be fine - the problem is just getting the delegate to actually get called when I call setNeedsDisplay(), and getting all of the sublayers to get drawn.
I've checked to be sure that each sublayer has the correct bounds and frame dimensions, and I've checked to make sure that setNeedsDisplay() gets called after each accelerometer point is acquired.
If I attach an instrument, I see that the frame rate is usually zero, but occasionally it will be some higher number.
My guess is that the run loop isn't cycling. There's actually nothing in the run loop, and I'm not sure where to put one. In the ViewDidLoad delegate, I set up an update rate for the accelerometer, and call a function that updates the sublayers in the view. Everything else is event driven, so I don't know what I'd do with a run loop.
I've tried creating CALayers, and adding them as sublayers. I've also tried making the GraphViewSegment class a UIView, so it has it's own layer.
The version that's written in Objective C works perfectly reliably.
The way that this application works, is that acceleration values show up on the left side of the screen, and scroll to the right. To make it efficient, new acceleration values are written into a small sublayer that holds a graph for 32 time values. When it's full, that whole sublayer is just moved a pixel at a time to the right, and a new (or recycled) segment takes its place at the left side of the screen.
Here's the code that moves unchanged segments to the right by a pixel:
for s: GraphViewSegment in self.segments {
var position = s.layer.position
position.x += 1.0;
s.layer.position = position;
//s.layer.hidden = false
s.layer.setNeedsDisplay()
}
I don't think that the setNeedsDisplay is strictly necessary here, since it's called for the layer when the segment at the left gets a new line segment.
Here's how new layers are added:
public func addSegment() -> GraphViewSegment {
// Create a new segment and add it to the segments array.
var segment = GraphViewSegment(coder: self.coder)
// We add it at the front of the array because -recycleSegment expects the oldest segment
// to be at the end of the array. As long as we always insert the youngest segment at the front
// this will be true.
self.segments.insert(segment, atIndex: 0)
// this is now a weak reference
// Ensure that newly added segment layers are placed after the text view's layer so that the text view
// always renders above the segment layer.
self.layer.insertSublayer(segment.layer, below: self.text.layer)
// Position it properly (see the comment for kSegmentInitialPosition)
segment.layer.position = kSegmentInitialPosition;
//println("New segment added")
self.layer.setNeedsDisplay()
segment.layer.setNeedsDisplay()
return segment;
}
At this point I'm pretty confused. I've tried calling setNeedsDisplay all over the place, including the owning UIView. I've tried making the sublayers UIViews, and I've tried making them not be UIViews. No matter what I do, the behavior is always the same.
Everything is set up in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
pause.possibleTitles?.setByAddingObjectsFromArray([kLocalizedPause, kLocalizedResume])
isPaused = false
useAdaptive = false
self.changeFilter(LowpassFilter)
var accelerometerQueue = NSOperationQueue()
motionManager.accelerometerUpdateInterval = 1.0 / kUpdateFrequency
motionManager.startAccelerometerUpdatesToQueue(accelerometerQueue,
withHandler:
{(accelerometerData: CMAccelerometerData!, error: NSError!) -> Void in
self.accelerometer(accelerometerData)})
unfiltered.isAccessibilityElement = true
unfiltered.accessibilityLabel = "unfiltered graph"
filtered.isAccessibilityElement = true
filtered.accessibilityLabel = "filtered graph"
}
func accelerometer (accelerometerData: CMAccelerometerData!) {
if (!isPaused) {
let acceleration: CMAcceleration = accelerometerData.acceleration
filter.addAcceleration(acceleration)
unfiltered!.addPoint(acceleration.x, y: acceleration.y, z: acceleration.z)
filtered!.addPoint(filter.x, y: filter.y, z: filter.z)
//unfiltered.setNeedsDisplay()
}
}
Any idea?
I quite like Swift as a language - it takes the best parts of Java and C#, and adds some nice syntactic sugar. But this is driving me spare! I'm sure it's some little thing that I've overlooked, but I can't figure out what.
Since you've created a new NSOperationQueue for your accelerometer updates handler, everything that handler calls is also running in a separate queue, sequestered from the main run loop. I'd suggest either running that handler on the main thread NSOperationQueue.mainQueue() or moving anything that could update the UI back to the main thread via a block on the main queue:
NSOperationQueue.mainQueue().addOperationWithBlock {
// do UI stuff here
}

Resources