UIPageControl: dots with rectangular format - ios

I use native UIPageControl in my objective-c application and I want to have a rectangular dots instead of circular one. I searched about that and for a library providing this but I didn’t found anything.
I tried to do it like this but it has no effect:
-(void)updateDotsFormat {
for (int i = 0; i < [self.pageControl.subviews count]; i++)
{
UIView* dot = [self.pageControl.subviews objectAtIndex:i];
UIView* dotView = [[UIView alloc] init];
dotView.backgroundColor = [UIColor blueColor];
CGRect frame = dotView.frame;
frame.size.width = 50;
frame.size.height = 20;
dotView.frame = frame;
[dot addSubview:dotView];
}
}
How can I set the dots format to rectangle?

and I want to have a rectangular dots instead of circular one. I searched a that and for a library providing this but I didn’t found anything
Well, you would have to design your own view/control, since UIPageControl has no such ability. There are libraries for such a thing, but asking for one is not permitted on Stack Overflow; or you can write it yourself.

Related

How to find the center of a UIView after superviews have been transformed?

NOTE: code written in browser; probably not perfectly accurate, but it should give the general idea.
I've got a stack of views, something like this:
CGRect theFrame = CGRectMake(0, 0, 10, 10);
UIView *v1 = [UIView alloc] initWithFrame: theFrame];
UIView *v2 = [UIView alloc] initWithFrame: theFrame];
UIView *v3 = [UIView alloc] initWithFrame: theFrame];
// Set all the background colors, so I can see them: snip
// set all the clipsToBounds = NO, so I can place however I want: snip
v2.center = CGPointMake(100, 80);
[v1 addSubview: v2];
v3.center = CGPointMake (57, 42);
[v2 addSubview: v3];
v1.center = CGPointMake (193, 44);
[self.view addSubview: v1];
// etc., time passes, user presses TEST button
CGAffineTransform sXfrm = CGAffineTransformMakeScale(3.5, 4.7);
CGAffineTransform rXfrm = CGAffineTransformMakeRotation(M_PI / 3.);
v1.transform = CGAffineTransformConcat(sXfrm, rXfrm);
// etc., rotate & scale v2, while we're at it. snip
// Leave v3 unrotated & unscaled.
Ok, at this point, everything is displaying on the screen exactly as desired. My question is:
Where (that is: where, in self.view's coordinate space) is v3.center?
Here's some code that gives the CLOSE answer, but not-quite right, and I can't seem to figure out what's wrong with it:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView = [[UIView alloc] initWithFrame: testRect];
testView.backgroundColor = [UIColor greebColor];
[self.view addSubview: testView];
CGPoint center = v1.center;
#if 1 // Apply Transform
CGPoint ctr2 = CGPointApplyAffineTransform(v2.center, v1.transform);
#else // use layer
CGPoint ctr2 = CGPointMake ((v2.layer.frame.origin.x + (v2.layer.frame.size.width / 2.)),
(v2.layer.frame.origin.y + (v2.layer.frame.size.height / 2.)) );
ctr2 = CGPointApplyAffineTransform(ctr2, v1.transform);
#endif
center.x += ctr2.x;
center.y += ctr2.y;
CGPoint ctr3 = CGPointApplyAffineTransform(v3.center, CGAffineTransformConcat(v2.transform, v1.transform));
center.x += ctr3.x;
center.y += ctr3.y;
testView.center = center; // I want this to lay on top of v3
[self.view addSubview: testView];
NOTE: In my actual code, I put in-between test-views at all the intermediate centers. What I get is: testView1 (center) is correct, testView2 (ctr2) is off by a little, testView3 (ctr3) is off by a little more.
The #if is because I was experimenting with using ApplyAffine vs layer. Turns out they both give the same result, but it's a tad off.
Hopefully clarifying image:
You can use the UIView's convert points methods:
convertPoint:toView:
convertPoint:fromView:
convertRect:toView:
convertRect:fromView:
However once you apply a transformation, you should stop using frames and use center + bounds instead (which might be the reason your code is not working), from apple docs (for frame):
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
The only other thing you have to be conscious about is, which view invoques the convert to what other view since the results change. (source coordinate system -> target coordinate system.)
EDIT:
Thanks to this answer and Chiquis' comments in the question, my (Olie's) final (working) code looks like this:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView[3];
for (int ii = 0 ; ii < 3 ; ++ii)
{
testView[ii] = [[UIView alloc] initWithFrame: testRect];
testView[ii].backgroundColor = [UIColor redColor];
[self.view addSubview: testView[ii]];
}
testView[0].center = v0.center;
testView[1].center = [v0 convertPoint: v1.center toView: self.view];
testView[2].center = [v1 convertPoint: v2.center toView: self.view];
To clarify some, v1 is a subview of v0, and v2 is a subview of v1; this dictates the receivers in the covertPoint: calls.
I try to let iOS do the heavy lifting for me when I need to transform points between different layers.
CGPoint point = v3.center;
CGPoint ctr3 = [v3.layer.presentationLayer convertPoint:point toLayer:self.layer.presentationLayer];
The presentation layer object represents the state of the layer as it currently appears onscreen. This can help avoid timing issues if animations are involved.
Just noticed the comment that came in while I was composing: yes, I use this to account for rotation transforms similar to what you describe.

UILabel (CALayer) is using large amounts of virtual memory

In Xcode and Instruments I see UILabel (CALayer) using large amounts of virtual memory (Anonymous VM). I see about 235 KB of virtual memory per UILabel.
I think this perhaps is a new issue with iOS 7.1 or 7.1.1.
Is this expected?
I created a simple program that creates 500 UILabels and Instruments shows 115MB of memory used. At about 1500 labels the application is terminated by the OS.
for (int i = 0; i < 500; i++)
{
index = (int)[self.items count];
index++;
frame = CGRectMake(10.0, 20, 300.0, 50.0);
UILabel *newLabel = [[UILabel alloc] initWithFrame:frame];
newLabel.text = [NSString stringWithFormat:#"This is text for label: %d", index];
newLabel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:newLabel];
[self.items setObject:newLabel forKey:[NSNumber numberWithInteger:index]];
}
Thoughts?
UILabel, and any view that uses drawRect (at least on iOS 7+) is backed by a texture, so every UILabel will use a lot of memory, the larger the label, the more memory used.
I've found this especially painful in my photo editing extension for You Doodle which allows adding text in the Photos app. Unfortunately I have to greatly restrict the scale range of the text because photo extensions are much more limited to memory usage than regular apps before they crash.
This can easily be verified by running instruments and allocating a bunch of large UILabel's and setting their text and ensuring they are all visible, i.e.:
CGRect f = CGRectMake(0.0.0f, 0.0f, 300.0f, 300.0f);
for (NSInteger i = 0; i < 100; i++)
{
UILabel* l = [[UILabel alloc] initWithFrame:f];
l.backgroundColor = UIColor.blueColor;
l.textColor = UIColor.whiteColor;
l.text = #"UILabel text for the memory test";
l.numberOfLines = 0;
[self.view addSubview:l];
}
When reporting this kind of thing (to Stack Overflow or to Apple), you really should eliminate unnecessary excess code. This code is sufficient to reproduce the phenomenon:
for (int i = 0; i < 500; i++)
{
CGRect frame = CGRectMake(10.0, 20, 300.0, 50.0);
UILabel *newLabel = [[UILabel alloc] initWithFrame:frame];
newLabel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:newLabel];
}
That causes the app to use 129MB on my machine. (No need to use Instruments: Xcode now shows memory usage directly.)
My first response was: "I guess I don't find this terribly surprising. If you change the frame to a smaller rect, less memory is used. Views are expensive! They are backed by a bitmap."
However, if you change the UILabel to a plain UIView, only 13MB is used. I think that's a sufficient difference to warrant filing a bug.

Spawn UIImageView So That It Does Not Intersect With Existing UIImageViews

Basically, I have an app where a user dodges bombs and collects coins. When a user collects a coin, another coin is spawned. I want each coin to not spawn on a bomb. (By the way these are all uiimageviews). In the code below, I have an array of my bombs called bombArray and the UIImageview called "one" is the coin UIImageView. I know the code below doesn't work, but what other method(s) could I use? Thanks, and here is the code:
UIImageView *one = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"goldCoin.png"]];
CGRect rectOne = CGRectMake(arc4random() % (900), arc4random() % (700), 40, 40);
[one setFrame:rectOne];
[self.view addSubview:one];
for (UIImageView* two in bombArray)
{
while (CGRectIntersectsRect(two.frame, one.frame))
{
one.center=CGPointMake(arc4random() % (900), arc4random() % (700));
}
}
*Note: I completely understand why the code above does not work, but I cannot find another solution to my problem.
First of all to make such a thing I'd use the new SpriteKit that gives you physics (and thus collision detection) out of the box.
But if you want to do this with standard UIViews what you can do for being absolutely sure that images don't collide between them is to divide the container in a grid and then loop through each cell of your grid and "flip a coin" to randomly put a coin or not in that cell. Then if you want to fill more space instead of dividing for rectangular cells you can divide for hexagonal cells, in this way you will cover more space.
So a fast and absolutely not too much checked version could be something like this:
int x = 0;
int y = 0;
int side = 40;
int it = 0;
do {
do {
CGRect imageFrame = CGRectMake(x, y, side, side);
UIImageView* imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.borderWidth = 1.0;
imageView.layer.cornerRadius = side / 2;
imageView.layer.masksToBounds = YES;
[self.containerView addSubview:imageView];
BOOL flip = arc4random() & 0x1;
if (flip) {
[imageView setImage:[UIImage imageNamed:#"coin_image"]];
}
} while ((x+=side) < (self.containerView.frame.size.width - side / 2));
it++;
if (it % 2 != 0) {
x = side / 2;
} else {
x = 0;
}
} while ((y+=(side * 0.85)) < (self.containerView.frame.size.height - side) );
Mind that this is NOT efficient, it is executed in the main thread and doesn't take in account possible (actually probable) memory constraints.

Multiple views with shadow and rotation becomes very slow

I'm trying to make multiple stacks of uiview's. Each of those UIView's with a shadow, slight rotation and scale. As a test I'm making 10 stack's of 10 views. Drawing all this is very slow.. Is there a good way to optimise this? I tried making the shadow and background from a image but that was ugly and equally as slow. I put these stacks in a UIScrollView
for (int k = 0; k < 9; k++) {
for (int i = 0; i < 9; i++) {
UIView *stack = [[UIView alloc] initWithFrame:CGRectMake((i * 106), (k * 106), 110, 110)];
for (int i = 0; i < 10; i++) {
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
//cardView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"card_background.png"]];
cardView.layer.shadowOffset = CGSizeMake(0, 0);
cardView.layer.shadowOpacity = 0.3f;
cardView.layer.shadowPath = [UIBezierPath bezierPathWithRect:(CGRect){CGPointZero, cardView.layer.bounds.size}].CGPath; // Set shadow path, without this the performance is *really* bad
cardView.transform = CGAffineTransformMakeRotation(((arc4random() % 20) - 10.0f) / 100.0f);
cardView.transform = CGAffineTransformScale(cardView.transform, 0.35, 0.35);
cardView.layer.shouldRasterize = YES;
cardView.layer.rasterizationScale = 0.5;
cardView.center = CGPointMake(55, 55);
[stack addSubview:cardView];
}
[_backgroundView addSubview:stack];
}
}
Edit 1; Tried some stuff, disabling rasterzation isn't helping much, disabling the shadow doesn't help much either, rotation and scaling are recourse intensive with this much uiview's too. Would async drawing (one stack at the time) be an option?
Edit 2; Guess making 100 UIView's is just slow anyway. I'll report back if I've found a better (I guess async or something like that) solution
Are you using these images in some sort of animation? If not you can try turning off rasterizing to increase performance.
cardView.layer.shouldRasterize = NO;
Shadows are very computationally intensive. If shouldRasterize doesn't do it, make a UIImage for the shadow, and if your views can change size, load the image with resizableImageWithCapInsets.

How to add UIView to UIScrollView dynamically if previous UIView is full

I have to do a pdf generator with custom label, so in my storyboard I have a UIScrollView with a UIView inside it, and if my UIView is full, I want to add another UIView and fill it with other label that is not already in. How can I add another page in my UIScrollView with another UIView ? I have to do it programmatically or there is an option in storyboard for doing it ?
I tried this (I don't know if it works and I don't want to compile it 'cause I find it really really ugly) :
CGFloat placement = titleLabel.frame.size.height + kMarginLabel;
for(int i = 0; i < [contract.nameContract count]; i++)
{
if(placement > _contractPageView.frame.size.height)
{
CGFloat secondPlacement = 0;
_scrollView.contentSize = CGSizeMake(_contractPageView.frame.size.width * 2, _contractPageView.frame.size.height);
UIView *secondPage = [[UIView alloc] initWithFrame:_contractPageView.frame];
articleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0 + kMarginLabel, secondPlacement, kTitleHeight, kTitleWidth)];
articleLabel.text = [NSString stringWithFormat:#"Article %i - %#",i, [contract.nameContract objectAtIndex:i]];
[secondPage addSubview:articleLabel];
secondPlacement += articleLabel.frame.size.height;
clauseLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, secondPlacement + kMarginLabel, _contractPageView.frame.size.width, kSizeOfClause)];
clauseLabel.text = [contract.textContract objectAtIndex:i];
secondPlacement += clauseLabel.frame.size.height;
[secondPage addSubview:clauseLabel];
[_scrollView addSubview:secondPage];
if(secondPlacement > secondPage.frame.size.height)
{
CGFloat thirdPlacement = 0;
_scrollView.contentSize = CGSizeMake(_contractPageView.frame.size.width * 3, _contractPageView.frame.size.height);
UIView *thirdPage = [[UIView alloc] initWithFrame:_contractPageView.frame];
articleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0 + kMarginLabel, thirdPlacement, kTitleHeight, kTitleWidth)];
articleLabel.text = [NSString stringWithFormat:#"Article %i - %#",i, [contract.nameContract objectAtIndex:i]];
[thirdPage addSubview:articleLabel];
thirdPlacement += articleLabel.frame.size.height;
clauseLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, thirdPlacement + kMarginLabel, _contractPageView.frame.size.width, kSizeOfClause)];
clauseLabel.text = [contract.textContract objectAtIndex:i];
thirdPlacement += clauseLabel.frame.size.height;
[thirdPage addSubview:clauseLabel];
[_scrollView addSubview:thirdPage];
}
} else {
articleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, placement, kTitleHeight, kTitleWidth)];
articleLabel.text = [NSString stringWithFormat:#"Article %i - %#",i, [contract.nameContract objectAtIndex:i]];
[_contractPageView addSubview:articleLabel];
placement += articleLabel.frame.size.height;
clauseLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, placement + kMarginLabel, _contractPageView.frame.size.width, kSizeOfClause)];
clauseLabel.text = [contract.textContract objectAtIndex:i];
placement += clauseLabel.frame.size.height;
[_contractPageView addSubview:clauseLabel];
}
}
I don't know if it's the only method, and imagine if I have many and many many many object in my array, I have to create more and more page :/ I don't know how to do it :(
Thanks for your help.
I did some similar code for a generic core data editor for one of our projects. So as I see it you got two options:
Use a collection view design the prototype view as your page and hook it up to an array controller that manages your text. The text could be organised as an array of NSAttributedString that you could bind to a NSTextView (one for each page)
If you need more flexibility, i.e. pages of different sizes, different layouts etc.., you could program your own container that keep as properties an array of "Pages" (you would need to define this class) and whenever this array is changed recomputes the sizes and constraints by, for example by adding up the heights of every pages and sizing your content view appropriately.
Hope it helps

Resources