Move view within superview's bounds (iOS) - ios

In an app I am attempting to have a view move within another view to a random location. I have been able to do this as follows:
mySmallerView.center = randomizeLocation()
func randomizeLocation() -> CGPoint {
let randomX = arc4random_uniform(UInt32(mainView.frame.width))
let randomY = arc4random_uniform(UInt32(mainView.frame.height))
return CGPointMake(CGFloat(randomX), CGFloat(randomY))
}
This moves it around quite nicely, BUT it uses center, so sometimes, the left is off the screen, or the right, top and/or bottom go off the screen because its center can be pushing the limits of the view's frame.
How would I improve randomizeLocation() to where it would ensure the BOUNDS/FRAME of the view to be moved do not exceed the bounds/frame of its superview?
Is there a way to do this?

i think it should be like this the max randomX must not able to be the mianView.frame.width
func randomizeLocation() -> CGPoint {
let randomX = arc4random_uniform(UInt32(mainView.frame.width - (mySmallerView.frame.width))) + (mySmallerView.frame.width / 2)
let randomY = arc4random_uniform(UInt32(mainView.frame.height - (mySmallerView.frame.height))) + (mySmallerView.frame.height / 2)
}

I think you need to make sure you don't put your smaller view too far out or down (by adding - (mySmallerView.frame.width / 2)). You also want to make sure you don't go too far to the left or up (by adding + (mySmallerView.frame.width / 2)).
func randomizeLocation() -> CGPoint {
let randomX = arc4random_uniform(UInt32(mainView.frame.width - (mySmallerView.frame.width / 2))) + (mySmallerView.frame.width / 2)
let randomY = arc4random_uniform(UInt32(mainView.frame.height - (mySmallerView.frame.height / 2))) + (mySmallerView.frame.height / 2)
return CGPointMake(CGFloat(randomX), CGFloat(randomY))
}

Related

ScrollView inertia effect manually, iOS, Swift

I have UICollectionView which I'm dragging from code (don't ask me why it's very long story:)).
And my code is working pretty well:
func move(prevPoint: CGPoint, curPoint: CGPoint) {
let xDiff = curPoint.x - prevPoint.x
let yDiff = curPoint.y - prevPoint.y
let xSign = xDiff == 0 ? 1 : (xDiff / abs(xDiff))
let ySign = yDiff == 0 ? 1 : (yDiff / abs(yDiff))
let x = max(min(abs(xDiff), maxPickerStep), minPickerStep) * -xSign * xMultiplier
let y = max(min(abs(yDiff), maxPickerStep), minPickerStep) * -ySign
let offset = CGPoint(x: collectionView.contentOffset.x + x, y: collectionView.contentOffset.y)
let cell = (collectionView.visibleCells.first as? ColorsCollectionViewCell)
let innerOffset = cell?.colorCollectionView.contentOffset ?? .zero
let inset = (cell?.colorCollectionView.contentInset.top ?? 0) * 2
let innerYContentOffset = min(max(innerOffset.y + y, -inset), (cell?.colorCollectionView.contentSize.height ?? 0) - inset)
cell?.colorCollectionView.contentOffset = CGPoint(x: innerOffset.x, y: innerYContentOffset)
collectionView.contentOffset = offset
}
But in addition to scrolling, I want to achieve the same effect as in UICollectionView when scrollView moves by inertia after user takes away finger. Thanks.
First thing first, I think that moving the scroll view manually is most certainly a thing I would avoid.
Probably there is something much simpler to fulfill the behavior you need.
So I highly suggest you, and any other reader, to not go further in the reading of this post and, instead, go ahead and try to solve the problem that guided you here in the first place.
You could also ask another question here on Stack Overflow to maybe get help to try to avoid you to manually update the scrollView position.
So if you are still reading, this article is probably the way to go with implementing something that really feels like a UIScrollView. Doing anything else will probably really look and feel awful.
Basically it consists of using UIKit Dynamics to control the inertia.
So you can create an object that conforms to UIDynamicItem (with a non-zero CGRect), and change its center instead of the scrollView contentOffset, than use a UIDynamicAnimator and its UIDynamicBehavior to set up the inertia and to connect the changes during the animation to the corresponding contentOffset in the scrollView using the UIDynamicBehavior's action block.
Assuming that you have an item that is a UIDynamicItem, and an animator that is a UIDynamicAnimator, the handling of the panGesture recognizer would look something like this:
func handlGestureRecognizer(panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began:
self.animator.removeAllBehaviors()
case .changed:
// Update scroll view position
break
case .ended:
var velocity = panGesture.velocity(in: panGesture.view!)
velocity.x = -velocity.x
velocity.y = -velocity.y
// You probably need to check for out of bound velocity too, and also put velocity.x to 0 if the scroll is only scrolling vertically
// This is done to just save the current content offset and then change it alongside the animation from this starting point
item.center = scrollView.contentOffset
let decelerationBehavior = UIDynamicItemBehavior(items: [item])
decelerationBehavior.addLinearVelocity(velocity, for: item)
decelerationBehavior.resistance = 2.0
decelerationBehavior.action = {
// Connect the item center to the scroll contentOffset. Probably something like this:
scrollView.contentOffset = item.center
}
self.animator.addBehavior(decelerationBehavior)
default:
break
}
}
You than just need to play up with the values of the behavior and be careful with the velocity you put into the behavior having extra care in looking at the edge cases (if you scroll over the min/max for example)
PS: After all I've written, I still believe you should strongly consider not doing this and, instead, go with the standard scrollView scrolling, avoiding manual updates.
You can try to play with decelerationRate and see if it satisfies your needs.
collectionView.decelerationRate = UIScrollView.DecelerationRate(rawValue: 1)

Convert UIView transform from View A to View B

What is the best way to convert a view's transform relative to another view's perspective?
Example:
View A has 2 child's, View B and C. View B also has a child, View D.
View Hierarchy
A
/ \
B C
/
D
The transform on B is not identity and D is "identity", but only if D is looked at isolated (not taking into account the transform on B).
View C has to know what the transform of view D is, relatively. In C perspective D is not identity.
I have been thinking about UICoordinateSpace and it's convert methods. How can I create similar covert methods for transform?
Help is very much appreciated :)
#Brandon Suggested that I should use the UICoordinateSpace convert method to convert the view's frame to the other view's CoordinateSpace and from there get the transform from the frame difference. That solution works for me but note that this does not take into account the rotationAngle, it will instead scale the transform to fit the rotated frame as it is not rotated.
Solution
extension UIView {
/// Returns transform for translation and scale difference from self and given view.
func convertScaleAndTranslation(to view: UIView) -> CGAffineTransform {
return CGAffineTransform.from(frame, to: convert(frame, to: view))
}
}
extension CGAffineTransform {
/// Returns transform for translation and scale difference from two given rects.
static func from(_ from: CGRect, to: CGRect) -> CGAffineTransform {
let sx = to.size.width / from.size.width
let sy = to.size.height / from.size.height
let scale = CGAffineTransform(scaleX: sx, y: sy)
let heightDiff = from.size.height - to.size.height
let widthDiff = from.size.width - to.size.width
let dx = to.origin.x - widthDiff / 2 - from.origin.x
let dy = to.origin.y - heightDiff / 2 - from.origin.y
let trans = CGAffineTransform(translationX: dx, y: dy)
return scale.concatenating(trans)
}
}
#Brandon thank you for the help :)

iOS MapKit - Detect when map is fully zoomed in?

So I am using a clustering library to group annotations and there is a small bug with it whereby some very close together annotations can appear grouped when the map is fully zoomed in. With this being a framework I can't do much about it directly but I can disable all grouping if the map is fully zoomed in. The problem is I can't work out a reliable way of doing this.
Here is my regionDidChangeAnimated code which is ideally where I would like to check if the map is fully zoomed in (to the point where you cant zoom in any more).
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
NSOperationQueue().addOperationWithBlock {
let scale: Double = Double(self.map.bounds.size.width) / self.map.visibleMapRect.size.width
let annotations = self.clusteringManager?.clusteredAnnotationsWithinMapRect(self.map.visibleMapRect, withZoomScale: scale)
self.clusteringManager?.displayAnnotations(annotations, onMapView: self.map)
}
}
I have tried inspecting the mapView.region.span property but I'm sure this will change depending on screen size etc...
Any suggestions? Thanks in advance.
You need to extend your MKMapView:
class YourMapView : MKMapView {
// function returns current zoom level of the map
func getCurrentZoom() -> Double {
var angleCamera = self.camera.heading
if angleCamera > 270 {
angleCamera = 360 - angleCamera
} else if angleCamera > 90 {
angleCamera = fabs(angleCamera - 180)
}
let angleRad = M_PI * angleCamera / 180
let width = Double(self.frame.size.width)
let height = Double(self.frame.size.height)
let offset : Double = 20 // offset of Windows (StatusBar)
let spanStraight = width * self.region.span.longitudeDelta / (width * cos(angleRad) + (height - offset) * sin(angleRad))
return log2(360 * ((width / 256) / spanStraight)) + 1;
}
}
Now your able to read out the current Zoom Level out in the following delegate methods:
regionDidChangeAnimated
And
regionWillChangeAnimated

Keeping an object within the screen. Swift SpriteKit

I am new to swift a Sprite kit. In the app I am trying to make I have a submarine moving through the ocean. Every time the user clicks the screen the gravity starts pulling the sub in the opposite direction. My problem is that i can't find a way to keep the sub from leaving the screen. I have tried to solve it by making a physicsBody around the screen, but the sub still leaves the screen. I have also tried the following code in the updateCurrentTime fund.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.physicsWorld.gravity = CGVectorMake(0,gravity)
if (sub.position.y >= self.size.height - sub.size.height / 2){
sub.position.y = self.size.height - self.sub.size.height / 2
}
if (sub.position.y <= sub.size.height / 2) {
sub.position.y = self.sub.size.height / 2
}
}
But this doesn't do anything either.
Any help would be greatly appreciated!!!!! thanks in advance!
P.S. I can't believe that it is that hard to keep things on the screen!!!
frustrating!
Try SKConstraint - it doesn't require a physicsBody. The code would look something like this, and would constrain the sub sprite to the scene:
let width2 = sub.size.width/2
let height2 = sub.size.height/2
let xRange = SKRange(lowerLimit:0+width2,upperLimit:size.width-width2)
let yRange = SKRange(lowerLimit:0+height2,upperLimit:size.height-height2)
sub.constraints = [SKConstraint.positionX(xRange,Y:yRange)]
Try this in the update:
if sub.frame.maxY >= view!.frame.height {
sub.position.y = view!.frame.height - sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
if sub.frame.minY <= 0 {
sub.position.y = sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
And then inside of the event where you want to reverse gravity don't forget to do this:
sub.physicsBody!.affectedByGravity = true
Alternatively, instead of using gravity you could use this which is a better option in my opinion:
// This moves the object to the top of the screen
let action = SKAction.moveToY(view!.frame.height - character.size.height / 2, duration: 5.0) // Or however much time you want to the action to run.
action.timingMode = .EaseInEaseOut // Or something else
character.runAction(action)
// Change view!.frame.height - character.size.height / 2 to just character.size.height / 2 to move to the bottom.

iOS : Adaptive Collection View cell width

I'm trying to make a grid view on iOS with adaptive column width.
On Android it's possible to do this by setting stretchMode attribute to spacingWidth .
This attribute take the cell width as minimum width and grow cell automatically if there is free space available but no enough to add another column keeping the same space between column everywhere.
I didn't found any way to do that on iOS.
It look like that (left image) on iPhone 6, but on iPhone 5 (right image) the space is very big and ugly. I wan't to auto resize cells to avoid this big space.
How can i do that on iOS ?
(I'm using Xcode 6.1)
Thanks
EDIT :
This i why i wan't (black space is approximatively desired additional cell width, sorry my draw is ugly I did it quickly)
EDIT 2:
I tried to calculate new size with this code, but the result is "strange" (wrong size, position) , i think i missed something
let CELL_SIZE : Float = 92
let CELL_MARGIN : Float = 10
let COLLECTION_VIEW_MARGIN : Float = 20 //Left right margin
let screenWidth = Float(UIScreen.mainScreen().bounds.width)
let numberOfCell = Float(Int(screenWidth / (CELL_SIZE + CELL_MARGIN + COLLECTION_VIEW_MARGIN)))
let oldCellSize = Float(cell.frame.width)
var newCellSize : Float
if(numberOfCell >= 2){
newCellSize = (screenWidth / numberOfCell) - (CELL_MARGIN * (numberOfCell-1))
} else {
newCellSize = (screenWidth / numberOfCell) }
let indexPathRow = Float(indexPath.row)
var xOffsetMultiplier = indexPathRow % numberOfCell
if(xOffsetMultiplier == 0){
xOffsetMultiplier = numberOfCell
}
var newX : Float = 0
if(xOffsetMultiplier == 1){
newX = COLLECTION_VIEW_MARGIN / 2
} else {
newX = (newCellSize + CELL_MARGIN) * (xOffsetMultiplier-1) + (COLLECTION_VIEW_MARGIN / 2)
}
var frame = CGRectMake(CGFloat(newX), cell.frame.minY, CGFloat(newCellSize), cell.frame.height)
cell.frame = frame
This code is written in func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell in my ViewController
You should use UICollectionViewFlowLayout to adopt cell size according to collection view size.

Resources