Objective-C Check If Subviews of Rotated Uiviews Intersect

Objective-C check if subviews of rotated UIViews intersect?

The following algorithm can be used to check if two (rotated or otherwise transformed) views overlap:

  • Use [view convertPoint:point toView:nil] to convert the 4 boundary points of both views
    to a common coordinate system (the window coordinates).
  • The converted points form two convex quadrilaterals.
  • Use the SAT (Separating Axis Theorem) to check if the quadrilaterals intersect.

This: http://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf is another description of the algorithm containing pseudo-code, more can be found by googling for "Separating Axis Theorem".


Update: I have tried to create a Objective-C method for the "Separating Axis Theorem", and this is what I got. Up to now, I did only a few tests, so I hope that there are not too many errors.

- (BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2;

tests if 2 convex polygons intersect. Both polygons are given as a CGPoint array of the vertices.

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2

tests (as described above) if two arbitrary views intersect.

Implementation:

- (void)projectionOfPolygon:(CGPoint *)poly count:(int)count onto:(CGPoint)perp min:(CGFloat *)minp max:(CGFloat *)maxp
{
CGFloat minproj = MAXFLOAT;
CGFloat maxproj = -MAXFLOAT;
for (int j = 0; j < count; j++) {
CGFloat proj = poly[j].x * perp.x + poly[j].y * perp.y;
if (proj > maxproj)
maxproj = proj;
if (proj < minproj)
minproj = proj;
}
*minp = minproj;
*maxp = maxproj;
}

-(BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2
{
for (int i = 0; i < count1; i++) {
// Perpendicular vector for one edge of poly1:
CGPoint p1 = poly1[i];
CGPoint p2 = poly1[(i+1) % count1];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

// Projection intervals of poly1, poly2 onto perpendicular vector:
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

// If projections do not overlap then we have a "separating axis"
// which means that the polygons do not intersect:
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}

// And now the other way around with edges from poly2:
for (int i = 0; i < count2; i++) {
CGPoint p1 = poly2[i];
CGPoint p2 = poly2[(i+1) % count2];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}

// No separating axis found, then the polygons must intersect:
return YES;
}

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
{
CGPoint poly1[4];
CGRect bounds1 = view1.bounds;
poly1[0] = [view1 convertPoint:bounds1.origin toView:nil];
poly1[1] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y) toView:nil];
poly1[2] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y + bounds1.size.height) toView:nil];
poly1[3] = [view1 convertPoint:CGPointMake(bounds1.origin.x, bounds1.origin.y + bounds1.size.height) toView:nil];

CGPoint poly2[4];
CGRect bounds2 = view2.bounds;
poly2[0] = [view2 convertPoint:bounds2.origin toView:nil];
poly2[1] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y) toView:nil];
poly2[2] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y + bounds2.size.height) toView:nil];
poly2[3] = [view2 convertPoint:CGPointMake(bounds2.origin.x, bounds2.origin.y + bounds2.size.height) toView:nil];

return [self convexPolygon:poly1 count:4 intersectsWith:poly2 count:4];
}

Swift version. (Added this behaviour to UIView via an extension)

extension UIView {

func projection(of polygon: [CGPoint], perpendicularVector: CGPoint) -> (CGFloat, CGFloat) {
var minproj = CGFloat.greatestFiniteMagnitude
var maxproj = -CGFloat.greatestFiniteMagnitude

for j in 0..<polygon.count {
let proj = polygon[j].x * perpendicularVector.x + polygon[j].y * perpendicularVector.y
if proj > maxproj {
maxproj = proj
}

if proj < minproj {
minproj = proj
}
}

return (minproj, maxproj)
}

func convex(polygon: [CGPoint], intersectsWith polygon2: [CGPoint]) -> Bool {
//
let count1 = polygon.count
for i in 0..<count1 {
let p1 = polygon[i]
let p2 = polygon[(i+1) % count1]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)

let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1

let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m2.1

if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
let count2 = polygon2.count
for i in 0..<count2 {
let p1 = polygon2[i]
let p2 = polygon2[(i+1) % count2]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)

let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1

let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m1.0

if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
return true
}

func intersects(with someView: UIView) -> Bool {
//
var points1 = [CGPoint]()
let bounds1 = bounds
let p11 = convert(bounds1.origin, to: nil)
let p21 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y), to: nil)
let p31 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y + bounds1.size.height) , to: nil)
let p41 = convert(CGPoint(x: bounds1.origin.x, y: bounds1.origin.y + bounds1.size.height), to: nil)
points1.append(p11)
points1.append(p21)
points1.append(p31)
points1.append(p41)
//
var points2 = [CGPoint]()
let bounds2 = someView.bounds
let p12 = someView.convert(bounds2.origin, to: nil)
let p22 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y), to: nil)
let p32 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y + bounds2.size.height) , to: nil)
let p42 = someView.convert(CGPoint(x: bounds2.origin.x, y: bounds2.origin.y + bounds2.size.height), to: nil)
points2.append(p12)
points2.append(p22)
points2.append(p32)
points2.append(p42)
//
return convex(polygon: points1, intersectsWith: points2)
}

Detecting collisions between rotated UIViews

when you rotate a view, its bounds won't change but its frame changes.

So, for my view with backgroundColor blue,

the initial frame i set to was

frame = (30, 150, 150, 35);

bounds={{0, 0}, {150, 35}};

but after rotating by 45 degree, the frame changed to

frame = (39.5926 102.093; 130.815 130.815);

bounds={{0, 0}, {150, 35}};

screenshot of running app showing frame of blue view with black border

Because the frame always return the smallest enclosing rectangle of that view.

So, in your case, even-though it looks both views are not intersecting,their frames intersect.

To solve it you can use, separating axis test.
If you want learn on it, link here

I tried to solve it and finally got the solution.
If you like to check, below is the code.
Copy paste the below code into an empty project to check it out.

In .m file

@implementation ViewController{
UIView *nonRotatedView;
UIView *rotatedView;
}

- (void)viewDidLoad
{
[super viewDidLoad];
nonRotatedView =[[UIView alloc] initWithFrame:CGRectMake(120, 80, 150, 40)];
nonRotatedView.backgroundColor =[UIColor blackColor];
[self.view addSubview:nonRotatedView];

rotatedView =[[UIView alloc] initWithFrame:CGRectMake(30, 150, 150, 35)];
rotatedView.backgroundColor =[UIColor blueColor];
[self.view addSubview:rotatedView];
CGAffineTransform t=CGAffineTransformMakeRotation(M_PI_4);
rotatedView.transform=t;

CAShapeLayer *layer =[CAShapeLayer layer];
[layer setFrame:rotatedView.frame];
[self.view.layer addSublayer:layer];
[layer setBorderColor:[UIColor blackColor].CGColor];
[layer setBorderWidth:1];

CGPoint p=CGPointMake(rotatedView.bounds.size.width/2, rotatedView.bounds.size.height/2);

p.x = -p.x;p.y=-p.y;
CGPoint tL =CGPointApplyAffineTransform(p, t);
tL.x +=rotatedView.center.x;
tL.y +=rotatedView.center.y;

p.x = -p.x;
CGPoint tR =CGPointApplyAffineTransform(p, t);
tR.x +=rotatedView.center.x;
tR.y +=rotatedView.center.y;

p.y=-p.y;
CGPoint bR =CGPointApplyAffineTransform(p, t);
bR.x +=rotatedView.center.x;
bR.y +=rotatedView.center.y;

p.x = -p.x;
CGPoint bL =CGPointApplyAffineTransform(p, t);
bL.x +=rotatedView.center.x;
bL.y +=rotatedView.center.y;

//check for edges of nonRotated Rect's edges
BOOL contains=YES;
CGFloat value=nonRotatedView.frame.origin.x;
if(tL.x<value && tR.x<value && bR.x<value && bL.x<value)
contains=NO;
value=nonRotatedView.frame.origin.y;
if(tL.y<value && tR.y<value && bR.y<value && bL.y<value)
contains=NO;
value=nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width;
if(tL.x>value && tR.x>value && bR.x>value && bL.x>value)
contains=NO;
value=nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height;
if(tL.y>value && tR.y>value && bR.y>value && bL.y>value)
contains=NO;

if(contains==NO){
NSLog(@"no intersection 1");
return;
}
//check for roatedView's edges
CGPoint rotatedVertexArray[]={tL,tR,bR,bL,tL,tR};

CGPoint nonRotatedVertexArray[4];
nonRotatedVertexArray[0]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y);
nonRotatedVertexArray[1]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y);
nonRotatedVertexArray[2]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);
nonRotatedVertexArray[3]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);

NSInteger i,j;
for (i=0; i<4; i++) {
CGPoint first=rotatedVertexArray[i];
CGPoint second=rotatedVertexArray[i+1];
CGPoint third=rotatedVertexArray[i+2];
CGPoint mainVector =CGPointMake(second.x-first.x, second.y-first.y);
CGPoint selfVector =CGPointMake(third.x-first.x, third.y-first.y);
BOOL sign;
sign=[self crossProductOf:mainVector withPoint:selfVector];
for (j=0; j<4; j++) {
CGPoint otherPoint=nonRotatedVertexArray[j];
CGPoint otherVector = CGPointMake(otherPoint.x-first.x, otherPoint.y-first.y);
BOOL checkSign=[self crossProductOf:mainVector withPoint:otherVector];
if(checkSign==sign)
break;
else if (j==3)
contains=NO;
}
if(contains==NO){
NSLog(@"no intersection 2");
return;
}
}
NSLog(@"intersection");
}

-(BOOL)crossProductOf:(CGPoint)point1 withPoint:(CGPoint)point2{
if((point1.x*point2.y-point1.y*point2.x)>=0)
return YES;
else
return NO;
}

Hope this helps.

How to know if subViews intersect While moving with UIGesturerescognizer or some otherway?

Try brute force first. You might be surprised at how well it does. (But remember that a loop through [[button superview] subviews] will contain the button itself, so it will always stop because the button intersects itself. Be sure to exclude the button).

Optimize after you have something working that is demonstrably slow with real data.

If that's really the case, there's a whole lot of algorithmic work done on this problem, which can be summarized as preprocessing the data into structures that allow cheaper initial tests to reject distant objects. This is a good SO answer on the topic, referring to this article.

Determine if crop rect is entirely contained within rotated UIView

That should be easier than checking for intersection (as in the referenced thread).

The (rotated) image view is a convex quadrilateral. Therefore it suffices to check
that all 4 corner points of the crop rectangle are within the rotated image view.

  • Use [cropView convertPoint:point toView:imageView] to convert the corner points of the crop rectangle to the coordinate system of the
    (rotated) image view.
  • Use CGRectContainsPoint() to check that the 4 converted corner points are within the bounds rectangle of the image view.

Sample code:

- (BOOL)rotatedView:(UIView *)rotatedView containsCompletely:(UIView *)cropView {

CGPoint cropRotated[4];
CGRect rotatedBounds = rotatedView.bounds;
CGRect cropBounds = cropView.bounds;

// Convert corner points of cropView to the coordinate system of rotatedView:
cropRotated[0] = [cropView convertPoint:cropBounds.origin toView:rotatedView];
cropRotated[1] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y) toView:rotatedView];
cropRotated[2] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];
cropRotated[3] = [cropView convertPoint:CGPointMake(cropBounds.origin.x, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];

// Check if all converted points are within the bounds of rotatedView:
return (CGRectContainsPoint(rotatedBounds, cropRotated[0]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[1]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[2]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[3]));
}

Objective-C check if subviews of rotated UIViews intersect?

The following algorithm can be used to check if two (rotated or otherwise transformed) views overlap:

  • Use [view convertPoint:point toView:nil] to convert the 4 boundary points of both views
    to a common coordinate system (the window coordinates).
  • The converted points form two convex quadrilaterals.
  • Use the SAT (Separating Axis Theorem) to check if the quadrilaterals intersect.

This: http://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf is another description of the algorithm containing pseudo-code, more can be found by googling for "Separating Axis Theorem".


Update: I have tried to create a Objective-C method for the "Separating Axis Theorem", and this is what I got. Up to now, I did only a few tests, so I hope that there are not too many errors.

- (BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2;

tests if 2 convex polygons intersect. Both polygons are given as a CGPoint array of the vertices.

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2

tests (as described above) if two arbitrary views intersect.

Implementation:

- (void)projectionOfPolygon:(CGPoint *)poly count:(int)count onto:(CGPoint)perp min:(CGFloat *)minp max:(CGFloat *)maxp
{
CGFloat minproj = MAXFLOAT;
CGFloat maxproj = -MAXFLOAT;
for (int j = 0; j < count; j++) {
CGFloat proj = poly[j].x * perp.x + poly[j].y * perp.y;
if (proj > maxproj)
maxproj = proj;
if (proj < minproj)
minproj = proj;
}
*minp = minproj;
*maxp = maxproj;
}

-(BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2
{
for (int i = 0; i < count1; i++) {
// Perpendicular vector for one edge of poly1:
CGPoint p1 = poly1[i];
CGPoint p2 = poly1[(i+1) % count1];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

// Projection intervals of poly1, poly2 onto perpendicular vector:
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

// If projections do not overlap then we have a "separating axis"
// which means that the polygons do not intersect:
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}

// And now the other way around with edges from poly2:
for (int i = 0; i < count2; i++) {
CGPoint p1 = poly2[i];
CGPoint p2 = poly2[(i+1) % count2];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}

// No separating axis found, then the polygons must intersect:
return YES;
}

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
{
CGPoint poly1[4];
CGRect bounds1 = view1.bounds;
poly1[0] = [view1 convertPoint:bounds1.origin toView:nil];
poly1[1] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y) toView:nil];
poly1[2] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y + bounds1.size.height) toView:nil];
poly1[3] = [view1 convertPoint:CGPointMake(bounds1.origin.x, bounds1.origin.y + bounds1.size.height) toView:nil];

CGPoint poly2[4];
CGRect bounds2 = view2.bounds;
poly2[0] = [view2 convertPoint:bounds2.origin toView:nil];
poly2[1] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y) toView:nil];
poly2[2] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y + bounds2.size.height) toView:nil];
poly2[3] = [view2 convertPoint:CGPointMake(bounds2.origin.x, bounds2.origin.y + bounds2.size.height) toView:nil];

return [self convexPolygon:poly1 count:4 intersectsWith:poly2 count:4];
}

Swift version. (Added this behaviour to UIView via an extension)

extension UIView {

func projection(of polygon: [CGPoint], perpendicularVector: CGPoint) -> (CGFloat, CGFloat) {
var minproj = CGFloat.greatestFiniteMagnitude
var maxproj = -CGFloat.greatestFiniteMagnitude

for j in 0..<polygon.count {
let proj = polygon[j].x * perpendicularVector.x + polygon[j].y * perpendicularVector.y
if proj > maxproj {
maxproj = proj
}

if proj < minproj {
minproj = proj
}
}

return (minproj, maxproj)
}

func convex(polygon: [CGPoint], intersectsWith polygon2: [CGPoint]) -> Bool {
//
let count1 = polygon.count
for i in 0..<count1 {
let p1 = polygon[i]
let p2 = polygon[(i+1) % count1]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)

let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1

let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m2.1

if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
let count2 = polygon2.count
for i in 0..<count2 {
let p1 = polygon2[i]
let p2 = polygon2[(i+1) % count2]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)

let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1

let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m1.0

if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
return true
}

func intersects(with someView: UIView) -> Bool {
//
var points1 = [CGPoint]()
let bounds1 = bounds
let p11 = convert(bounds1.origin, to: nil)
let p21 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y), to: nil)
let p31 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y + bounds1.size.height) , to: nil)
let p41 = convert(CGPoint(x: bounds1.origin.x, y: bounds1.origin.y + bounds1.size.height), to: nil)
points1.append(p11)
points1.append(p21)
points1.append(p31)
points1.append(p41)
//
var points2 = [CGPoint]()
let bounds2 = someView.bounds
let p12 = someView.convert(bounds2.origin, to: nil)
let p22 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y), to: nil)
let p32 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y + bounds2.size.height) , to: nil)
let p42 = someView.convert(CGPoint(x: bounds2.origin.x, y: bounds2.origin.y + bounds2.size.height), to: nil)
points2.append(p12)
points2.append(p22)
points2.append(p32)
points2.append(p42)
//
return convex(polygon: points1, intersectsWith: points2)
}

Determine whether UIView is covered by other views?

Conceptually, you want to iterate down the stack, unioning the paths of the cards into an accumulated path, and then checking that union path for equality with the next union path. In other words, if adding the next path down doesn't change the union path, then it must be completely obscured, and can therefore be removed. It might look something like this:

UIBezierPath* accumulator = nil;
for (UIView* cardView in [[[containingView subviews] copy] reverseObjectEnumerator])
{
UIBezierPath* p = GetPathForView(cardView);
UIBezierPath* next = PathByUnioningPaths(p, accumulator);

if ([next isEqual: accumulator])
{
// This view is completely obscured, remove it
[cardView removeFromSuperview];
}

accumulator = next;
}

This, of course, presumes the existence of the functions GetPathForView and PathByUnioningPaths. The former will be yours to write, based on however you create your card views. The latter will require some sort of bezier path boolean operation library. I found this one, which seems to have a fair bit of traction: https://bitbucket.org/martinwinter/vectorbooleancg

Rotate only view and not its subviews

One very simple solution would be to perform the reverse rotation on the subview:

[self setTransform:CGAffineTransformMakeRotation(angle * M_PI / -180.0)];
// NOTE: we're using the negative of the angle here
[self.myCallOutView setTransform:CGAffineTransformMakeRotation(- angle * M_PI / -180.0)]];

This will cancel out the effect of the rotation on the superview.

Alternative approach: structure your views a little differently: instead of having view A inside view B, have both views A and B being subviews of a container view C. Then just apply your rotation to view A, B won't be affected. And to move the whole ensemble on the display, relocate the container view C.



Related Topics



Leave a reply



Submit