I am using CGRectIntersectsRect to test if blueView (being dragged) has intersected with redView (stationary). However, I need to know if redView was intersected by blueView from red's top, bottom, right or left?
Is there a CG method to accomplish this?
I'm not aware of any CG Method to accomplish this.
The way I'd go about this is using CGRectIntersectsRect to check if they intersect at all, then if they do intersect, measure from the center of redView to blueView which would return the distance between them (X and Y), from that you could possibly tell from which angle the intersection was coming from?
If you wanted a little more accuracy you could measure from redView top center, to blueView bottom center, and the same for all 4 edges? Then work out which is the closest?
There are multiple ways you could approach this problem but I'm not aware of a 'one method fits all' solution.
Edit:
Was still thinking about this, I think the most accurate way would be something like this:
let leftDistance = blue.origin.x - (red.origin.x + red.width)
let rightDistance = (blue.origin.x + blue.width) - red.origin.x
let topDistance = blue.origin.y - (red.origin.y + red.height)
let bottomDistance = (blue.origin.y + blue.height) - red.origin.y
The the lowest of those distance = coming from the left, right, top or bottom.
(P.s. I might have gotten blue + Red the wrong way around, as well as top and bottom. I'd recommend just having a play around and logging these values while dragging the item)
Here is the solution I finally used, In case someone else is looking for it. Below line of code test if the intersection is vertical (top & bottom views intersected), where a and b are view.frame for each of the two view intersecting.
if ((a.maxY > q.maxY) && (b.minY < q.minY)) || ((a.maxY < q.maxY) && (b.minY > q.minY))
Related
Code
self.view.frame.origin.y = self.view.frame.origin.y - keyboardSize.height
Here I am trying to set my views origin to my view-height of the keyboard
My output
My Doubt
my view has to go below the keyboard according to math, what is happening?
The coordinate space that UIViews are in is not exactly the same as the cartesian coordinate plane you learned in math class, which might look something like this:
In UIKit, the positive y direction is downwards, and the negative y direction is upwards. This means that a view with a small y coordinate will be at the top of its superview. As you increase its y coordinate, it moves downwards. It looks something like this:
This line of code:
self.view.frame.origin.y=self.view.frame.origin.y-keyboardSize.height
decreases the y coordinate of self.view, hence moving it up. By how much? Well, the keyboard's height.
View coordinate system increaes from top (0) to bottom (device height) , so this
self.view.frame.origin.y=self.view.frame.origin.y-keyboardSize.height
raises the view as it decrease it's y origin position
This is an example view:
I want to calculate a frame with a CGPoint where I can spawn another card(UIView) without touching any existing card. Ofcourse it is optional since the view can be full of cards, therefore there is no free spot.
This is how I can see any card on the screen and my function how it is now:
func freeSpotCalculator() -> CGPoint?{
var takenSpots = [CGPoint]()
for card in playableCards{
takenSpots.append(card.center)
}
}
I have no idea where to start and how to calculate a random CGPoint on the screen. The random frame has the same width and height as a card in on the screen.
The naive approach to this is very simple, but could be problematic once the screen fills up. Generate a random CGPoint with x coordinate between 0 and the screen width and a y coordinate between 0 and the screen height. Check if a rectangle with a center at that point intersects any existing view. If it does not, you have your random position.
Where this gets problematic is when the screen starts to fill up. At that point you could be trying many many random points before finding a place to put the card. You could also reach a situation where no more cards will fit. How do you know that you have reached that? Will your loop generating the random points just run forever?
A smarter solution is to keep track of the free spaces on the screen. Always generate your random points roughly within these free spaces. You could do this using a grid if approximate is close enough. Is there a card occupying each grid location? Then when the largest free space is smaller than the size of your card rectangle, you know you're done. It's a lot more work than the naive approach, but it's faster when the screen starts to fill up and you'll know for sure when you're done.
If you know that you will always have more screen space than the cards can possibly take up, the naive approach is fine.
The idea
You know the width and height of your container UIView. And, each card has the same width and height. I would go about this by calculating a grid.
Even though you want to display cards randomly, relying on a grid will give you a standardized array of centers that you can use to generate the appearance of randomness (place a card at any random center that is a part of the grid, for example).
If you were to place a card at truly any random location, you might just want to use CGRectIntersectsRect(card1.frame, card2.frame) to detect collisions.
The pattern
First, let's store the card width and height as constants.
let cardWidth = card.bounds.size.width
let cardHeight = card.bounds.size.height
As a basic proof of concept, let's say your container view width is 250 points. Let's say the card width is 5 points. That means you can fit 250 / 5 = 50 cards in one row, where one row has the height of one card.
The number of centers in a row = the number of cards in that row. Each center is the same distance apart. In the following diagram (if I can even call it that), the [ and ] represent edges of a row. The -|- represents a card, where | is the center of the card.
[ - | - - | - - | - - | - - | - ]
Notice how every center is two dashes away from the next center. The only consideration is that the center next to the edge is one dash away from the edge. In terms of cards, each center is one whole card away from the next, and the centers next to the edges are one half card away from the edges.
The key to the pattern
This pattern means that the x position of any card center in a specific row = (cardWidth / 2) + (the card index * cardWidth). In fact, this pseudo-equation works for y positions as well.
The code
Here's some Swift that creates an array of centers using this method.
var centers = [CGPoint]()
let numberOfRows: CGFloat = containerView.bounds.size.height / cardHeight
let numberOfCardsPerRow: CGFloat = containerView.bounds.size.width / cardWidth
for row in 0 ..< Int(numberOfRows) {
for card in 0 ..< Int(numberOfCardsPerRow) {
// The row we are on affects the y values of all the centers
let yBasedOnRow = (cardHeight / 2) + (CGFloat(row) * cardHeight)
// The xBasedOnCard formula is effectively the same as the yBasedOnRow one
let xBasedOnCard = (cardWidth / 2) + (CGFloat(card) * cardWidth)
// Each possible center for this row gets appended to the centers array
centers.append(CGPoint(x: xBasedOnCard, y: yBasedOnRow))
}
}
This code should create a grid of centers for your cards. You could build a function around it that returns a random center for a card to be placed and keeps track of used centers.
Potential improvements
First, I think that the centers array could be made a matrix ([[CGPoint]]()) for more logical storage of points.
Second, this code currently makes the assumption that the width and height of the container view are divisible by the card width and height. For example, a container width of 177 and a card width of 5 would result in some problems. The code could be fixed a number of different ways to account for this.
Best solution simplest/performance is to display card randomly BUT inside a grid. The trick is to have the grid bigger than the card size, so the card position inside the grid will be random.
Easy to check which position is occupy and cards will be on "random" frames.
1- Create a Collection View Controller with the total number of card u want to display (lets say.. max card that enter in the screen?)
2- Set the prototype cell size bigger than the card. If the card is 50x80 then the cell should be 70x110.
3- Add a UIImageView to the cell with constraints, this will be your card image
4- Create a UICollectionViewCell, with a method that set the card frames randomly inside the cell (modify the constraints)
Done!
Cells with no card will have no image or an empty cell as you wish. So to add a new card, just do a random between the empty cells and add the card with its random coordinates inside the cell.
Your UICollectionViewCell would like like this
class CardCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var card: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
let newX = CGFloat(arc4random_uniform(UInt32(bounds.size.width-card.bounds.size.width+1)))
let newY = CGFloat(arc4random_uniform(UInt32(bounds.size.height-card.bounds.size.height+1)))
card.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: newX).active = true
card.topAnchor.constraintEqualToAnchor(rightAnchor, constant: newY).active = true
}
}
And your Collections View Controller should like like this
Collection View Image
As I can see in the picture all your cards are aligned at the bottom of the View. so if you generate a random y position from 0 to origin of your cards row - one card height you can simply get a CGPoint based on the frame of your view and size of the cards.
If you want to randomly place cards along the screen, you could do something like this:
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
let actualX = random(min: *whatever amount*, max: *whatever amount* )
let actualY = random(min: *Height of the first card*, max: *whatever amount* )
card.position = CGPoint(x: actualX, y: actualY )
The cards will then be positioned randomly above the existing cards.
I am not sure if you are planning to place all the cards in an orderly way. But if you do, you could do it like this.
Once the view is loaded, get all the possible card positions and store them in a map together with a number used as the key. Then you could generate a random number from 0 to the total number of possible card positions that you stored in the map. Then every time you occupy a position, clear a value from the map.
You can try with CAShapeLayer and UIBezierPath.
Create a CAShapeLayer for your main view where you will be adding sub views. Let's call it as main shape layer. This will be helpful to check the new view estimated is within the main view.
Create a UIBezierPath instance. Whenever a valid new sub view is found, add the edges to this path.
Create a random point within the main view.
Create a CGRect based on random point as center of your sub view. Let’s call it as estimated view frame.
Check the estimated view frame is completely visible in your main view. Else go to step 3.
Check your 4 edges of your estimated view frame with path object. If any one of the edge is inside the path, go to step 3.
If 4 edges are not inside the path, the estimated view frame is the new view’s frame.
Create a new subview and add it to your main view.
Add the edges of new view to path.
I have created a sample project in swift with the above logic.
https://github.com/mcabasheer/find-free-space-in-uiview
You can change the width and height of new view. I have added a condition to stop looking for next free space after trying 50 times. This will help to avoid infinite loop.
As #davecom highlighted, taking a random number to add new view will waste the space and you will run out of space quickly. If you are able to maintain the free available space, you can add more sub views.
I have a UIView called container that I want to move (offset) using affine transfrom. This view contains UIImageView and is a subview of UICollectionViewCell.
So it should be simple:
container.transform = CGAffineTransformMakeTranslation(100, 200) //render container 100 points right and 200 points down
Instead it is very hard, because theat code does not do anything. The view is rendered excatly on the same place as if I delete that line. So I added 'print' to verify what affine translation was set:
container.transform = CGAffineTransformMakeTranslation(100, 200)
print(container.transform) //prints: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 200.0)
That seems all right. So I tried rotating the container view instead with CGAffineTransformMakeRotation and it rotates the view just not around its center as it should according to documentation. I tried different combinations of translate, rotation and scale transforms just to find that the affine transformation matrixes set are OK, but attributes tx and ty seems to be ignored and a, b, c and d seems to be using different anchor point then the centre of the view (cannot say what that point is).
Any ideas on what can be causing this and how to fix it?
There must be something like auto layout messing things up for you. In the absence of outside influence, setting a view's affine transform to CGAffineTransformMakeTranslation(100, 200) will shift it right 100 points and down 200. I verified this by making a new Single View Project in Xcode and changing the viewDidLoad method in the ViewController.swift class to:
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.blueColor();
let container = UIView(frame: CGRectMake(0,0,100,100));
container.backgroundColor = UIColor.greenColor();
container.transform = CGAffineTransformMakeTranslation(100, 200);
view.addSubview(container);
}
As expected this makes the green container view appear 100 points to the right and 200 points down, even though its frame is (0,0,100,100).
So please check for auto layout and other such things that might influence the placement of this view, and if you can't find anything please post more code. Also, if your container view doesn't have a background color, please give it one so that you can see its position directly, instead of deducing its position by looking at the image view.
n.b. Setting a view's transform doesn't actually move the view itself, it just changes how/where it draws its content.
widthEssentially, i am creating objects in Flash using actionscript. I currently have a problem where i am resizing the object in actionscript but as the point of reference seems to be the top left hand corner, the object when shrinking seems to also move towards the top left of the screen.
Is there a way to either code the point of origin for an object to the center of the object using code or to have it resize without seemingly moving?
my code for creating and resizing an object is like this (it will grow and shrink but at moment im just tryint to fix this problem before i move on):
var blob1:Loader = new Loader();
blob1.x = 1000;
blob1.y = 450;
blob1.load(new URLRequest("blob1.png"));
addChild(blob1);
blob1.addEventListener(Event.ENTER_FRAME, blobTween);
function blobTween(event:Event)
{
var size = 0;
if (size == 0){
blob1.width += 5;
blob1.height += 5;
}else if (size == 1){
}
Can't you update x and y while you change width and height ?
blob1.x += deltaWidth / 2
blob1.y += deltaHeight / 2
You increment width and height by 5, so deltaWidth = 5 and deltaHeight = 5 .
NOTE: deltaWidth / 2 doesn't divide equally, so you'll have slight shift. I suggest having deltaWidth = 6 and deltaHeight = 6 , or something similar.
GreenSock has a great library for doing just that; TransformManager.
http://www.greensock.com/transformmanageras3/
my solution in the end was a bit strange. I took the shape, divided it into 4 quarters and reattached them in flash by placing the objects in the same place and rotating them (as it rotates from the top left corner, the 4 quarters form a complete shape with each quarter having their top left corner in the centre of the "object"). I then resized them all at once to do it properly.
Whats weird is that the top right and bottom left corners required different math from the other two corners. Which makes no sense whatsoever. 2 corners required width and height to be adjusted. The other two corners required just width to be adjusted. very strange but worked :/
I'm creating a custom popover background, so I subclassed the UIPopoverBackground abstract class. While creating the layout function I came across a problem with placing and rotating the arrow for the background.
The first picture shows the arrow at the desired position. In the following code I calculated the origin I wanted but the rotation seemed to have translated the new position of the image off to the side about 11 points. As you can see, I created a hack solution where I shifted the arrow over 11 points. But that still doesn't cover up the fact that I have a gapping hole in my math skills. If someone would be so kind as to explain to me what's going on here I'd be eternally grateful. What also would be nice is a solution that would not involve magic numbers, so that I could apply this solution to the cases with the up, down and right arrow
#define ARROW_BASE_WIDTH 42.0
#define ARROW_HEIGHT 22.0
case UIPopoverArrowDirectionRight:
{
width -= ARROW_HEIGHT;
float arrowCenterY = self.frame.size.height/2 - ARROW_HEIGHT/2 + self.arrowOffset;
_arrowView.frame = CGRectMake(width,
arrowCenterY,
ARROW_BASE_WIDTH,
ARROW_HEIGHT);
rotation = CGAffineTransformMakeRotation(M_PI_2);
//rotation = CGAffineTransformTranslate(rotation, 0, 11);
_borderImageView.frame = CGRectMake(left, top, width, height);
[_arrowView setTransform:rotation];
}
break;
Well, if the rotation is applied about the center of the arrow view (as it is), that leaves a gap of (ARROW_BASE_WIDTH - ARROW_HEIGHT) / 2 to the post-rotation left of the arrow, which is what you have to compensate for, it seems. By offsetting the center of the arrow view by this much, it should come back into alignment.