I add multiple images and show them with zoom.
Due to limited memory size, I should try to remove some of them which has already seen with removeFromSuperview.
My problem is that when I try to zoom, selected image is disappeared by removeFromSuperview.
My codes are as below:
if ( [contentView viewWithTag:CONTENT_IMAGE_TAG_NUM + index] ) {
if (index > 2) {
UIView *prevContentView = [contentView viewWithTag:(CONTENT_IMAGE_TAG_NUM + index - 2)];
if (prevContentView) {
[prevContentView removeFromSuperview];
[[SDImageCache sharedImageCache] clearMemory];
}
}
} else {
UIImageView *imageView = [self addContentImage:index contentArray:contentArray prevImageHeight:prevImageHeight];
-----
}
When I try to zoom at 3rd image, it is disappeared.
To solve zoom problem, I removed 'removeFromSuperview' and could zoom well. But it made memory problem. I got memory warning. How can I solve this problem? I want to fix both zoom and memory issue.
Please let me know. Thanks in advance.
Cells are reused in table views and collection views, so you do not have to bother much about memory since the image view that you use will switch its image and therefore release previously retained image. SDImageCache on the other side should flush memory cache when memory warning received. So I guess you have a problem somewhere else.
Related
Running into a super weird bug in my iOS application I cannot figure out.
I load a UIImageView for a user in my iOS application and then round it into a circle.
- (void)viewDidLoad {
[super viewDidLoad];
self.profileImage.file = [[self.profileObject objectForKey:#"UserID"] valueForKey:#"ProfilePhoto"];
[self.profileImage loadInBackground:^(UIImage *image, NSError *error) {
self.profileImage.image = image;
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2;
self.profileImage.layer.masksToBounds = YES;
self.profileImage.contentMode = UIViewContentModeScaleAspectFill;
}];
}
This runs perfectly, until I dismiss that view and go back into it a few moments later.
- (void)dismissView {
[self dismissViewControllerAnimated:YES completion:nil];
self.profileImage.image = nil;
}
When I go back into the view, the image rounds from the point it was already rounded prior. Which means it then turns into somewhat of a diamond. See here: http://cl.ly/image/3p1P0M0M1d2H
Any ideas on why this would be happenig?
Workaround
I found that if I load the image in viewDidLoad and round it in viewDidAppear it works just fine. But that seems to "hacky" and doesn't load everything at the same time properly.
Any ideas on what I should try?
The problem is that you're loading the image using sone background threading technique, and the first time you do so, it has to presumably fetch the image from somewhere, the second time it presumably has a cached version so it can run the completion block immediately.
Why should this matter?
At viewDidLoad, under Autolayout, your image view's frame will be zero, yet you're using it to round the corners.
On the first run, the delay in loading the image is enough for the view to have performed a layout pass, so it can round properly. On the second run, it hasn't (because the image is cached), so it can't.
The solution, as you've already discovered, is to set the corner radius when layout has happened - either in viewDidLayoutSubviews or viewDidAppear. Setting the radius can be totally separate to loading the image, and isn't "hacky" at all.
A better solution would be to write an image view subclass that performed it's own corner rounding on layoutSubviews. It's not really the view controller's job to do that rounding.
try changing
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2;
to
self.profileImage.layer.cornerRadius = self.profileImage.bounds.size.width / 2;
in general it is always better to work internally with bounds rather than frame rect, because this is the internal perspective. self.bounds will reflect autoresizing, auto layout, rotation, scaling and other geometry issues which self.frame will not. Good luck
I am trying to overlay two images and put text on top in a view that I have. I have this working perfectly in ios7. Here is a screen shot of the results
Right now the gradient is simply an image on top of the other image as seen here in my layout
This works great except for when I test on my phone with ios6. Then everything goes nuts as seen here. *I've actually deleted the gradient layer and ran the app again and the background image remains the same size (about half of what it should be).
As you can see, the background image is only half of what it should be, and the second image is not overlaying. I've been at this for 5 hours and can't seem to find a solution that works.
Here is the code that sets the background image
-(void) SetDetails
{
if(_curInfo)
{
_lblTopName.text = _curInfo.company_name;
if(!_curInfo.img)
{
showActivity(self);
dispatch_queue_t aQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue1, ^{
_curInfo.img = getImageFromURL([NSString stringWithFormat:#"%#%#", g_serverUrl, _curInfo.imgPath]);
dispatch_async(dispatch_get_main_queue(), ^{
hideActivity();
[_imgCompany setImage:_curInfo.img];
});
});
}
[_imgCompany setImage:_curInfo.img];
/* FIX IMAGE SIZE */
_imgCompany.contentMode=UIViewContentModeScaleAspectFill;
CGRect photoFrame = _imgCompany.frame;
photoFrame.size = CGSizeMake(320, 180);
_imgCompany.frame=photoFrame;
[_imgCompany setClipsToBounds:YES];
_lblDistance.text = [NSString stringWithFormat:#"%.2f miles", _curInfo.distance];
_lblReward.text=_curInfo.reward;
CGFloat scrollViewHeight = 0.0f;
for (UIView* view in scroller.subviews)
{
scrollViewHeight += view.frame.size.height;
}
[scroller setContentSize:(CGSizeMake(320, scrollViewHeight))];
}
}
Any help is greatly appreciated. I'm not opposed to drawing the gradient onto the image either.
Additional Info:
Here is how I have the two image views setup.
You need to unstick the 'opaque' option on your image view, given that it isn't opaque.
As to the other spacing issue, I'd guess it's a mismatch between iOS 7 view controllers always acting as if they had wantsFullScreenLayout set to YES but the default having been NO under iOS 6. The rest of your image is probably underneath your navigation bar. It looks like you're part trying the interface builder and part doing programmatic layout — why did you add the code underneath FIX IMAGE SIZE and what happens if you remove it?
am trying to place a ScrollView in my app that has 1000,000 record, this scrollView will load when the app launches, so the app is not running until the million 1000 000 record which takes a lot of time, i was wondering is there any way to show the app and the scrollView while records are loading (show the scrollView while adding its records), below the code am using:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self loadIt];
}
- (void)loadIt{
float startX = 0;
float startY = 0;
[_bigScroll setContentSize:CGSizeMake(320, 312500)];
_bigScroll.pagingEnabled = NO;
for (counter=0; counter<999999; counter++)
{
UIButton *tester=[[UIButton alloc] initWithFrame:CGRectMake(startX, startY, 10, 10)];
if (counter % 2 == 0) {
[tester setBackgroundColor:[UIColor whiteColor]];
}
else
{
[tester setBackgroundColor:[UIColor grayColor]];
}
[_bigScroll addSubview:tester];
[tester release];
if (startX == 320) {
startX = 0;
startY += 10;
}
else
startX += 10;
NSLog(#"counter = %d", counter);
}
}
Please advice.
Is there any way to show the app and the scrollView while records are loading ?
Try to use [self performSelector:#selector(loadIt) withObject:nil]; or
[self performSelector:#selector(loadIt) withObject:nil afterDelay:0.2];
It will not block your UI until the execution of this method.
You are loading lots of records. Actually you should not load all records at at time. You should use mechanism something like tableview is using i.e.load only those record which are in visible area of scrollview. Don't load new rows until the scroll and you should reuse row or views so speedup the scrolling.
Apple's documentation for UIScrollView is very clear that the scrolled view should be tiled, with your application providing tiles as the view scrolls.
The object that manages the drawing of content displayed in a scroll view should tile the content’s subviews so that no view exceeds the size of the screen. As users scroll in the scroll view, this object should add and remove subviews as necessary.
This is necessary both for performance and memory usage: the scrollable view is backed by a CALayer, which in turn is backed by a bitmap. The same is true for each of the UIButton objects created.
Whilst it is not surprising that this takes a long time, it's more of a mystery that your app hasn't been terminated for using too much memory.
Both UITableView and UICollectionView are examples of views that tile their content. You may find you can use one of these to implement you requirements, and if not, follow the model they use.
You don't need to create 1000,000 views . You can create views dynamically and remove the previous views those are not visible at the screen space. So at the time of scrolling you can create new views and remove the views those are out of visible area of screen.
This will help you to save memory otherwise whether you are using ARC in your project if you load that much number of views in memory there will surely a chance of crash , ARC will not help you in that case.
once try this Change the code in the
-viewdidload()
{
[self loadIt];//change this to
[self performSelectorInBackground:#selector(loadIt) withObject:nil];
}
Are there any techniques to cause a UIWebView to redraw itself? I've tried setNeedsDisplay and setNeedsLayout on the UIWebView and its UIScrollView, but neither have worked.
Literally found the answer right after asking. The key was to tell the subviews of UIWebView's scrollView to redraw themselves - particularly the UIWebBrowserView.
- (void) forceRedrawInWebView:(UIWebView*)webView {
NSArray *views = webView.scrollView.subviews;
for(int i = 0; i<views.count; i++){
UIView *view = views[i];
//if([NSStringFromClass([view class]) isEqualToString:#"UIWebBrowserView"]){
[view setNeedsDisplayInRect:webView.bounds]; // Webkit Repaint, usually fast
[view setNeedsLayout]; // Webkit Relayout (slower than repaint)
// Causes redraw & relayout of *entire* UIWebView, onscreen and off, usually intensive
[view setNeedsDisplay];
[view setNeedsLayout];
// break; // glass in case of if statement (thanks Jake)
//}
}
}
I've commented out the if statement to be safe and avoid reliance on UIWebBrowserView's class name not changing. Without it, it hits all UIViews that are in the scrollview, which isn't really a problem at this point (no significant overhead incurred) but could always change.
EDIT:
In some cases, the following snippet of JavaScript will accomplish the same/similar thing:
window.scrollBy(1, 1); window.scrollBy(-1, -1);
You'd think UIScrollView's contentOffset would do this too, but that's not always the case in my experience - for some reason window.scrollTo is special in this regard.
Gist: https://gist.github.com/matt-curtis/5843862
This save my life:
self.wkWebView.evaluateJavaScript("window.scrollBy(1, 1);window.scrollBy(-1, -1);", completionHandler: nil)
Add this line on webview did load delegate event or wkwebview did finish navigation
Thanks MAN!!!!
I'm trying to increase the scrolling performance of my UIScrollView. I have a lot of UIButtons on it (they could be hundreds): every button has a png image set as background.
If I try to load the entire scroll when it appears, it takes too much time. Searching on the web, I've found a way to optimize it (loading and unloading pages while scrolling), but there's a little pause in scrolling everytime I have to load a new page.
Do you have any advice to make it scroll smoothly?
Below you can find my code.
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage=(int)(offset.y / 322.0f);
if(lastContentOffset>offset.y){
pageToRemove = currentPage+3;
pageToAdd = currentPage-3;
}
else{
pageToRemove = currentPage-3;
pageToAdd = currentPage+3;
}
//remove the buttons outside the range of the visible pages
if(pageToRemove>=0 && pageToRemove<=numberOfPages && currentPage<=numberOfPages){
for (UIView *view in scrollView.subviews)
{
if ([view isKindOfClass:[UIButton class]]){
if(lastContentOffset<offset.y && view.frame.origin.y<pageToRemove*322){
[view removeFromSuperview];
}
else if(lastContentOffset>offset.y && view.frame.origin.y>pageToRemove*322){
[view removeFromSuperview];
}
}
}
}
if(((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd) || (lastContentOffset>offset.y && lastPageToAdd-1==pageToAdd)) && pageToAdd>=0 && pageToAdd<=numberOfPages){
int tmpPage=0;
if((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd)){
tmpPage=pageToAdd-1;
}
else{
tmpPage=pageToAdd;
}
//the images are inside the application folder
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for(int i=0;i<4;i++){
UIButton* addButton=[[UIButton alloc] init];
addButton.layer.cornerRadius=10.0;
if(i + (tmpPage*4)<[imagesCatalogList count]){
UIImage* image=[UIImage imageWithContentsOfFile:[NSString stringWithFormat: #"%#/%#",docDir,[imagesCatalogList objectAtIndex:i + (tmpPage*4)]]];
if(image.size.width>image.size.height){
image=[image scaleToSize:CGSizeMake(image.size.width/(image.size.height/200), 200.0)];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2,(image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else if(image.size.width<image.size.height){
image=[image scaleToSize:CGSizeMake(200.0, image.size.height/(image.size.width/200))];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2, (image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else{
image=[image scaleToSize:CGSizeMake(159.5, 159.5)];
}
[addButton setBackgroundImage:image forState:UIControlStateNormal];
image=nil;
addButton.frame=CGRectMake(width, height, 159.5, 159.5);
NSLog(#"width %i height %i", width, height);
addButton.tag=i + (tmpPage*4);
[addButton addTarget:self action:#selector(modifyImage:) forControlEvents:UIControlEventTouchUpInside];
[tmpScrollView addSubview:addButton];
addButton=nil;
photos++;
}
}
}
lastPageToAdd=pageToAdd;
lastContentOffset=offset.y;
}
Here's a few recommendations:
1) First, understand that scrollViewDidScroll: will get called continuously, as the user scrolls. Not just once per page. So, I would make sure that you have logic that ensures that the real work involved in your loading is only triggered once per page.
Typically, I will keep a class ivar like int lastPage. Then, as scrollViewDidScroll: is called, I calculate the new current page. Only if it differs from the ivar do I trigger loading. Of course, then you need to save the dynamically calculated index (currentPage in your code) in your ivar.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll: method. I only trigger it there.
So, for example, if you take most of the code you posted and put it in a method called loadAndReleasePages, then you could do this in the scrollViewDidScroll: method, which defers the execution until after scrollViewDidScroll: finishes:
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage = (int)(offset.y / 322.0f);
if (currentPage != lastPage) {
lastPage = currentPage;
// we've changed pages, so load and release new content ...
// defer execution to keep scrolling responsive
[self performSelector: #selector(loadAndReleasePages) withObject: nil afterDelay:0];
}
}
This is code that I've used since early iOS versions, so you can certainly replace the performSelector: call with an asynchronous GCD method call, too. The point is not to do it inside the scroll view delegate callback.
3) Finally, you might want to experiment with slightly different algorithms for calculating when the scroll view has actually scrolled far enough that you want to load and release content. You currently use:
int currentPage=(int)(offset.y / 322.0f);
which will yield integer page numbers based on the way the / operator, and the float to int cast works. That may be fine. However, you might find that you want a slightly different algorithm, to trigger the loading at a slightly different point. For example, you might want to trigger the content load as the page has scrolled exactly 50% from one page to the next. Or you might want to trigger it only when you're almost completely off the first page (maybe 90%).
I believe that one scrolling intensive app I wrote actually did require me to tune the precise moment in the page scroll when I did the heavy resource loading. So, I used a slightly different rounding function to determine when the current page has changed.
You might play around with that, too.
Edit: after looking at your code a little more, I also see that the work you're doing is loading and scaling images. This is actually also a candidate for a background thread. You can load the UIImage from the filesystem, and do your scaling, on the background thread, and use GCD to finally set the button's background image (to the loaded image) and change its frame back on the UI thread.
UIImage is safe to use in background threads since iOS 4.0.
Don't touch a line of code until you've profiled. Xcode includes excellent tools for exactly this purpose.
First, in Xcode, make sure you are building to a real device, not the simulator
In Xcode, choose Profile from the Product menu
Once Instruments opens, choose the Core Animation instrument
In your app, scroll around in the scroll view you're looking to profile
You'll see the real time FPS at the top, and in the bottom, you'll see a breakdown of all function and method calls based on total time ran. Start drilling down the highest times until you hit methods in your own code. Hit Command + E to see the panel on the right, which will show you full stack traces for each function and method call you click on.
Now all you have to do is eliminate or optimize the calls to the most "expensive" functions and methods and verify your higher FPS.
That way you don't waste time optimizing blind, and potentially making changes that have no real effect on the performance.
My answer is really a more general approach to improving scroll view and table view performance. To address some of your particular concerns, I highly recommend watching this WWDC video on advanced scroll view use: https://developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview-techniques
The line that is likely killing your performance is:
addButton.layer.cornerRadius=10.0;
Why? Turns out the performance for cornerRadius is AWFUL! Take it out... guaranteed huge speedup.
Edit: This answer sums up what you should do quite clearly.
https://stackoverflow.com/a/6254531/537213
My most common solution is to rasterize the Views:
_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
But it works not in every situation.. Just try it