UIView creation and positioning - objective-c

I have in my controller two UIView members, progressLineView and buttonsView. At some point I call this method:
- (void) drawPlayProgressLine{
progressLineView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height)];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
Everything works fine, and I also have a method that changes the position of the view:
- (void) moveProgressLine{
CGRect frame = progressLineView.frame;
frame.origin.x++;
progressLineView.frame = frame;
}
After the moveProgressLine method is called a few times and I want to call drawPlayProgressLine again, instead of completely moving the view to the starting position, it creates a new view. The more drawPlayProgressLine is called, the more views I get on my screen but I only need one.
I don't understand how this can happen when I'm creating only one object. How can I move the view instead of having a new one created each time? And another question: how can completely remove it (until the drawPlayProgressLine method is called to create it again)

I don't understand how this can happen when I'm creating only one object.
You create a new view every time you call your -drawPlayProgressLine method. Call it 10 times, you get 10 views.
How can I move the view instead of having a new one created each time?
Don't create the view each time through -drawPlayProgressLine. Instead, you can do either of:
Create progressLineView once, when the view controller's view hierarchy is created. -viewDidLoad is a perfect place for that sort of thing.
Check the value of progressLineView and create it only if it is currently nil.
Whichever you choose, assuming progressLineView is an instance variable, you can do exactly what you're doing in your -moveProgressLine method. That is, just use progressLineView as though it already exists, because it does. BTW, an easy way to move a view is to modify it's center property:
CGPoint *c = progressLineView.center;
c.x += 25.0;
progressLineView.center = c;
And another question: how can completely remove it (until the
drawPlayProgressLine method is called to create it again)
One approach is to simply hide the view when you're not using it. Another is to remove it from its super view (and release it if you've retained it), and then set your progressLineView to nil. So, if progressLineView is an ivar, do this:
[progressLineView removeFromSuperview];
[progressLineView release]; // if you're not using ARC and have retained it
progressLineView = nil;

you should just check if its created yet before creating it and move it if necessary:
- (void) drawPlayProgressLine{
if(progressLineView == nil)
{
progressLineView = [[UIView alloc] init];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
progressLineView.frame = CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height);
}

You are probably not invalidating the parent view. New objects are not created, but rather their presentation is left on the screen after you move them.
As for the second question:
[progressLineView removeFromSuperview];
[progressLineView release];
progressLineView = nil;

Related

UIScrollView doesn't update properly, shows empty view randomly

I am trying to open a new view on select of a table cell in a previous view. The new view that I am trying to open, consists of different sub-views or modules. Hence, I populate each sub-view one by one inside in [self populate] method which is in triggered inside the viewDidLoad method.
-(void)viewDidLoad{
[super viewDidLoad];
[self populate];
}
-(void) populate{
[self.edgeGallery loadImagesWithURLs: _items];
// Modular view: main info
[self.vwListingMainView setListing: _listing];
[self.vwListingMainView refresh];
// Modular view: listing agents
_vwListingAgentsView.agentsArray = _listing.agents;
// Modular view: listing info
_vwListingInfoView.listing = _listing;
[_vwListingInfoView refresh];
// Modular view: Listing activities
_vwListingActivityView.listing = _listing;
[_vwListingActivityView requestCounts];
}
Every time a new subview is populated, the method viewWillLayoutSubViews is called. This is the method where I compute the subview's height and other constraints and append it to the superview.
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self computeAndFixHeight];
}
- (void) computeAndFixHeight {
// Adjusting each module's height
_cstMainInfoHeight.constant = [_vwListingMainView getViewHeight];
_cstListingActionsHeight.constant = [_vwListingActionsView getViewHeight];
_cstListingAgentsHeight.constant = [_vwListingAgentsView getViewHeight];
_cstListingInfoViewHeight.constant = [_vwListingInfoView getViewHeight];
// Adjusting scroll view height
NSInteger computedScrollHeight = _vwListingUpcomingEventView.frame.origin.y + [_vwListingUpcomingEventView getViewHeight];
[self.scrollView setContentSize:CGSizeMake(self.view.frame.size.width, computedScrollHeight)];
_cstContainerBottom.constant = -computedScrollHeight - kDefaultNegativeScrollH;
[self.view layoutIfNeeded];
[self.view updateConstraints];
}
However, once the view is loaded completely, the problem that I am facing is that sometimes, I get the complete view and sometimes, randomly, I get an empty view. I think [self.view layoutIfNeeded] is the problem, but I have also tried using [self.view setNeedsLayout] and [self.view setNeedsUpdateConstraints], but still the problem remains. Any help would be appreciated.
Please excuse me if I am doing anything stupid. I am new to iOS development.
I created this project just for your question to show how to update a scrollView just using AutoLayout (no need to override viewWillLayoutSubviews, update the scrollView's contentSize, call layoutIfNeeded or updateConstraints)
Hope it helps you =)
https://github.com/ghashi/ScrollViewQuestion/tree/master
This is the result:
In our project we had encountered similar issue where we had to add a lot of views to a content view of a scrollview using constraints added programatically. Writing constraints for each view not only made the view controller bloated, it was also static. To add another view we had to write the constraints again.
We end up creating subclass of UIView that now managed this for us. We named this NNVerticalStackView.h,.m.

Creating NSCollectionView with datasource programmatically

I am trying to create a NSCollectionView programmatically using a NSCollectionViewDataSource.
The code is very simple:
self.collectionView = [[NSCollectionView alloc] init];
// Add collection view to self.view etc.
self.collectionView.dataSource = self;
[self.collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:#"test"]
self.collectionView.collectionViewLayout = gridLayout;
[self.collectionView reloadData]
This leads to the following methods getting called (if I don't set the collectionViewLayout property explicitly these two don't get called either):
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView*)collectionView
- (NSInteger)collectionView:(NSCollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
However, collectionView:itemForRepresentedObjectAtIndexPath: is never called. Is there something else that I need to do in order to make sure that the last data source method is called? I have made sure that the two count calls return > 0, so that's not the problem.
So it seems that the problem was actually that I wasn't wrapping the NSCollectionView in a NSScrollView. This probably has to do with the layout being done incorrectly (so the items aren't requested from the data source) if it is not wrapped in a scroll view.
I've been working through different scenario's in the past days, and I dare say that using an NSScrollView, or not, makes practically no difference. With or without scrollView, I've ended up with the same errors and limitations.
What does make a huge difference is the choice between "old school" and the new-fangled collectionView. By "old school" I mean setting the itemPrototype and contents properties, something like this:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.itemPrototype = [TBCollectionViewItem new];
collectionView.content = self.collectionItems;
NSInteger index = 0;
for (NSString *title in _collectionItems) {
NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:0];
TBCollectionViewItem *item = [collectionView makeItemWithIdentifier:#"Test" forIndexPath:path];
item.representedObject = title;
index++;
}
// Plays well with constraints
New school, something along these lines:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.identifier = TBCollectionViewIdentifier;
[collectionView registerClass:[TBCollectionViewItem class] forItemWithIdentifier:TBCollectionViewItemIdentifier]; //register before makeItemWithIdentifier:forIndexPath: is called.
TBCollectionViewGridLayout *gridLayout = [TBCollectionViewGridLayout collectionViewGridLayout:NSMakeSize(250, 100)]; //getting the contentSize from the scrollView does not help
collectionView.collectionViewLayout = gridLayout;
collectionView.dataSource = self;
Now, you may have noticed the comment that registerClass: must be called before makeItemWithIdentifier:forIndexPath. In practice, that means calling registerClass: before setting .dataSource, whereas in your code you set .dataSource first. The docs state:
Although you can register new items at any time, you must not call the makeItemWithIdentifier:forIndexPath: method until after you register the corresponding item.
I wish I could say that by switching those two lines, all layout problems will be solved. Unfortunately, I've found that the .collectionViewLayout / .dataSource combination is a recipe for (auto)layout disaster. Whether that can be fixed by switching from NSCollectionViewGridLayout to flowLayout, I'm not yet certain.

Apply a shadow to an array of UIImageView

I have an array of UIImageViews. I want to apply a shadow to each of these images. I've used the code below:
- (void)awakeFromNib {
for (UIImageView *image in imagesJigsawPieces) {
image.layer.shadowColor = [UIColor blackColor].CGColor;
image.layer.shadowOffset = CGSizeMake(-1, -1);
image.layer.shadowOpacity = 1;
image.layer.shadowRadius = 5.0;
image.clipsToBounds = NO; //EDIT: I have also included this with no change
}
}
I have also included #import <QuartzCore/CALayer.h>.
I am not getting any errors but I am also not getting any shadows on my images.
Are you certain this code is being called? Have you placed a breakpoint in the for loop to verify?
-awakeFromNib is called only if you have a view (or whatever) in a nib file connected via IBOutlet to an ivar in your code. -awakefFromNib is called, in this case, instead of -initWithFrame: (or the like), an important distinction which I sometimes forget myself!

How to refresh view in objective-c

I wanted to create a gallery. It loads different images based on the category, that a user selects. I used to populate images in UIImageViews.
My when selecting different categories is that it does not clear the previously selected images. This is my code for populating images.
-(void)refresh{
categoryLabel.text = treatment.treatmentName;
NSMutableArray *galleryImages =[NSMutableArray alloc] ;
galleryImages = [[PatientImage alloc]find:treatment.treatmentId];
int imgCount = [galleryImages count];
for(int i=0;i<imgCount;i++){
PatientImage *apatientImage = [galleryImages objectAtIndex:i];
UIImage *img1 = [UIImage imageNamed:apatientImage.imageBefore];
UIImageView *myImageView = [[UIImageView alloc] initWithImage:img1];
myImageView.contentMode = UIViewContentModeTopRight;
myImageView.frame = CGRectMake(120+i*240,120.0,100.0, 100.0);
[self.view addSubview:myImageView];
UIImage *img2 = [UIImage imageNamed:apatientImage.imageAfter];
UIImageView *myImageView2 = [[UIImageView alloc] initWithImage:img2];
myImageView2.contentMode = UIViewContentModeTopRight;
myImageView2.frame = CGRectMake(120+i*240+300,120.0,100.0, 100.0);
[self.view addSubview:myImageView2];
}
}
First things first, You have some memory leaks there. You are allocating UIImageViews but are not releasing them anywhere, after you have added them to your view. I don't know if that applies to ARC, though. Same applies to your Mutable array, but I suppose you are releasing it after the 'for' loop somewhere, since it seems you posted code after omitting some of it.
As far as your actual question is concerned, I wouldn't do this this way. I would make the mutable array an object variable, and then fill it with my image views. When calling refresh again, I would first call -removeFromSuperview on each image view, then empty the array, then repopulate it and add the new subviews to my view. That is the simple way.
I don't know if you are using ARC, but you should be careful about memory management when using dynamically loaded views. Each time you add a view to another one, you increase its retain counter. You must then call release to remove ownership, and let the iOS runtime handle the rest.
Also note that operations such as this using views are expensive in terms of memory. So, another way of repopulating the gallery view is to just change the image an imageView holds. That will save you some memory, and time. In case the view doesn't have a constant number of images to be displayed, you can refine your algorithm to change the images on the already created image views, and then add more image views if necessary or delete the remaining ones, if any.
I hope I helped.
try at the start of refresh call
[[self subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
or
for (id imageView in self.subviews){
if([imageView isKindOfClass:[UIImageView class]]) [imageView removeFromSuperview];
}
call [tableview reloadData] if You are using tableview to show your gallery images
or call view's
[self.view setNeedsDisplay] method for refreshing the view.

Adding a sub-view appears to do nothing

In my application, when the user clicks an infoButton, it should add another view to the screen at a specific location. I'm trying to achieve this behavior with the following method:
- (IBAction)showInfo1:(id)sender
{
UIView *myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
[self.view addSubview:myView1];
}
(I declared everything in the header file of my class.)
When I run the code and press the button, nothing appears to happen (I don't see the new view).
I also noticed that XCode is displaying the following warning:
Local declaration of 'myView1' hides instance variable.
Does anyone have any ideas?
How do you know nothing changes?
It looks like myView1 doesn't actually contain anything. Try setting the background color of myView1
- (IBAction) showInfo1: (id) sender
{
UIView *myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
myView1.backgroundColor = [UIColor redColor];
[self.view addSubview: myView1];
}
To open it at a specific point you need to change the parameters in CGRectMake() for example
to open it at the very top left with a width of 50 and height of 20 you would do:
CGRectMake(0, 0, 50, 20)
"Local declaration of 'myView1' hides instance variable." message appears because you have declared in your class some property with the same name (even if the type is different).
If you put UIView *myView1 in your class definition, your method would look like
- (IBAction)showInfo1:(id)sender{
myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
[self.view addSubview:myView1];
}
Here comes more considerations: you must to release myView1 when you stop using it, avoid to be placed more than once, etc. but here we have the basic idea.
Finally, maybe your view is already added, but 'cause it doesn't contains anything yet, you don't notice it. Also you would like to check UIViewController to see if works better for you.