In my app I have this property (and its #synthesize in .m)
#property (nonatomic, strong) IBOutlet UILabel *titleHeader;
in a secondViewController.
The problem is that in iOS 7 if from firstViewController I do:
[secondViewController.titleHeader setText:#"title"];
it don't work, in iOS 6 it work.
why?
EDIT
I do it:
SecondViewController *secondViewController = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil code:[object code]];
[self.navigationController pushViewController:secondViewController animated:YES];
[secondViewController.titleHeader setText:[object name]];
[secondViewController.headerView setBackgroundColor:colorHeaderGallery];
The views of secondViewController have not been loaded yet when you set them.
You can verify this by querying for the value of secondViewController.titleHeader after pushing the view controller to the navigation stack.
As a fix, you can declare a property in secondViewController
#property (nonatomic, strong) NSString *titleHeaderText;
then set that property instead of secondViewController.titleHeader setText
e.g.
secondViewController.titleHeaderText = [object name];
After that, in your secondViewController's viewDidLoad method, you can set the text of your label.
[self.titleHeader setText:self.titleHeaderText ];
EDIT:
The behavior of pushViewController seems to have changed in iOS 7.
I have tried and replicated your issue.
If you do not want the above change, a workaround is by accessing the view of secondViewController, so that its views will be loaded before you call label setText.
e.g.
SecondViewController *secondViewController = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil code:[object code]];
[self.navigationController pushViewController:secondViewController animated:YES];
[secondViewController view];
[secondViewController.titleHeader setText:[object name]];
[secondViewController.headerView setBackgroundColor:colorHeaderGallery];
Related
I have been using core data to save the user's location for various times in a table view. Now that I can actually get the location details (coordinates mainly) at various times, I would like to plot the coordinates that are available at each time on a MapView (No overlays, just a single point to just point the location at that time). I have the coordinates (one latitude and one longitude value) available, but I would like to know how to plot them on the MapView.
Just to put my question simply, How one can plot the coordinates on a mapview ?
I just browsed through SO and I was not able to find the exact solution that would solve my problem ! Any help will be much appreciated.
Thanks for your time !
Regards,
Raj.
With the help of MKAnnotation, this problem can be solved. Thanks to #Craig and Vishal Kurup !
Create an annotation class as a subclass of NSObject.
in Annotation.h:
#interface Annotation : NSObject <MKAnnotation>
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, assign) NSString *title;
#property (nonatomic, assign) NSString *subtitle;
#end
It should conform to MKAnnotation and the three properties are the ones that are required when conforming to MKAnnotation class. Declare those and synthesize them in the .m file.
In the MapHistoryViewController implementation file, we need to add few lines of codes to view the annotation at the required coordinates.
MapHistoryViewController.m:
#interface MapHistoryViewController (){
CLLocationCoordinate2D annotationCoord;
Annotation *annotation;
}
#end
#implementation MapHistoryViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// To define coordinate for the annotation
annotationCoord.latitude = mapHistoryLocation.coordinate.latitude;
annotationCoord.longitude = mapHistoryLocation.coordinate.longitude;
annotation = [Annotation alloc];
annotation.coordinate = annotationCoord;
annotation.title = streetAnnotation;
// to display the annotation
[self.mapHistoryView addAnnotation:annotation];
// where mapHistoryView is my MKMapView object
}
//You can set the region too, if you want the map to be focused on the coordinates you have provided
//Hope this will help the people with the same question :)
I have prepared one view controller file named "CncWindowController" and In XIB file, i take window object (instead of UIView) and connected it to view outlet. So when I access its view, i get window object.
I'm accessing like it in AppDelegate.m file :
self.windowController = [[CncWindowController alloc] initWithNibName:#"CncWindowController" bundle:nil];
self.window = (UIWindow*) self.windowController.view;
Here, view is actually referring to window. But Rotation is not working in iOS6.0 and shouldAutorotate method is also not called.
If i will use window object like below then it works fine :
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
What is wrong with my approach ? Any help of ideas how i should work autorotation in iOS-6 too using above approach ?
Thanks!
Desired Scenario: create a custom Alert View via iOS6's AutoLayout vs Frames:
1) Create an empty UIView upon the host view (UIController.view):
alertView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[alertView(==300)]" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[alertView]-|" options:0 metrics:nil views:viewsDict]];
This works: I see my alert UIView upon (subview) the host view.
However, attempting to add UILabel to the Alert view bombs.
Seeing that the UIView (1) was drawn, I merely substituted the UILabel in its stead to see if I can get something:
- (UILabel *)createLabelWithMessage:(NSString *)message {
if (!message) return nil;
UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 0,0)];
myLabel.text = message;
[myLabel setFont:[UIFont systemFontOfSize:12.0]];
[myLabel setAutoresizingMask:UIViewAutoresizingNone];
myLabel.backgroundColor = [UIColor clearColor];
return myLabel;
}
...
titleLabel = [self createLabelWithMessage:#"Danger"];
...
// ...replacing 'alertView' (UIView) with 'titleView' (UILabel):
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[titleLabel(==300)]" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[titleLabel]-|" options:0 metrics:nil views:viewsDict]];
Question: Why would the UILabel bomb but the UIView appears to be drawing okay?
Here's the hint from Xcode:
AutoLayoutContraints[3828:11303] Unable to simultaneously satisfy
constraints.
Probably at least one of the constraints in the
following list is one you don't want. Try this:
(1) look at each
constraint and try to figure out which you don't expect;
(2) find the
code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you
don't understand, refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints) (
"",
"",
"" )
Will attempt to recover by breaking constraint
(Names: '|':UIView:0x128727a0 )>
There must be a hidden property within UILabel that's screwing up my 'Visual-Formatting' language.
..I created another subview of "alertView"; and I get the same error. So apparently I'm only getting a 'good' result when I merely display one (1) UIView (the 'alertView') upon (subview) of the UIController's view; nothing more.
Something hidden is conflicting the simple constraints. And I don't know what.
BTW: I'm using a NIB as the host UIView, with 'use autoLayout' 'ON'.
However, I'll be using this within larger code without 'autolayout' (iOS 4.3+), within
an iOS6-checked routine.
Solution: (per my comment) I had missed setting the 'setTranslatesAutoresizingMaskIntoConstraints' flag to 'NO' for the particular subview; albeit I had done it for its super container.
Example:
[testView setTranslatesAutoresizingMaskIntoConstraints:NO];
Don't forget to use 'setTranslatesAutoresizingMaskIntoConstraints' for ALL member UIViews!
I have some data that is fetched in another thread that updates a UICollectionView's header. However, I've not found an efficient way of reloading a supplementary view such as a header or footer.
I can call collectionView reloadSections:, but this reloads the entire section which is unnecessary. collectionView reloadItemsAtIndexPaths: only seems to target cells (not supplementary views). And calling setNeedsDisplay on the header itself doesn't appear to work either. Am I missing something?
You can also use (the lazy way)
collectionView.collectionViewLayout.invalidateLayout() // swift
[[_collectionView collectionViewLayout] invalidateLayout] // objc
More complex would be to provide a context
collectionView.collectionViewLayout.invalidateLayout(with: context) // swift
[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
You can then make a or configure the context yourself to inform about what should be updated see: UICollectionViewLayoutInvalidationContext
It has a function in there that you can override:
invalidateSupplementaryElements(ofKind:at:) // swift
Another option is (if you have already loaded the correct header/footer/supplementary view) and you only want to update the view with the new data than you can use one of the following functions to retrieve it:
supplementaryView(forElementKind:at:) // get specific one
visibleSupplementaryViews(ofKind:) // all visible ones
Same goes for visible cells with visibleCells. The advantage of just getting the view and not reloading a view entirely is that the cells retains it state. This is espically nice with table view cells when they use swipe to delete/edit/etc since that state is lost after reloading the cell.
If you feel fanatic you can of course also write some extensions to retrieve only cells/supplementary views of a given kind using generics
if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
configure(view, at indexPath)
}
this assumes that you have a function that registers/dequeues views in example with their class name. I made a post about this here
I just ran into the same problem, and I ended up looking up the view using its tag to edit a label:
UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];
Like you said it is unnecessary to reload an entire section, which cancels out any animation on cells as well. My solution isn't ideal, but it's easy enough.
Swift 3/4/5 version:
collectionView.collectionViewLayout.invalidateLayout()
Caution!
If you change the number of collectionView items at the same time (for example you show the footer only if all cells were loaded), it will crash. You need to reload the data first, to make sure that the number of items is the same before and after invalidateLayout():
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
I got the same problem. I tried #BobVorks's answer and it is working fine, if only the cell was reused else it won't. So, I tried finding a more cleaner way to achieve this and I came up reloading the whole UICollectionView after the performBatchUpdate (completion block) and it is working great. It reloads the Collection Without any cancellation of animation in the insertItemsAtIndexPath. Actually I personally up voted recent 2 answers cause i find it working but in my case, this is the cleanest way to do it.
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
Here are two ways you could do it.
1.
Create a mutable model to back the data that will eventually be available. Use KVO in inherited class of UICollectionReusableView to observe the changes and update the header view with the new data as it comes available.
[model addObserver:headerView
forKeyPath:#"path_To_Header_Data_I_care_about"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
then implement listener method in header view
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
2.
add notification listener to the view and post a notification when the data has successfully come available. Downside is that this is application wide and not a clean design.
// place in shared header file
#define HEADER_DATA_AVAILABLE #"Header Data Available Notification Name"
// object can contain userData property which could hole data needed.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(headerDataAvailable:) name:HEADER_DATA_AVAILABLE object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:HEADER_DATA_AVAILABLE object:nil];
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
I've used above method to get current header, and successfully updated subviews on it.
Here's what I did to update only the section headers that are currently loaded in memory:
Add a weakToStrong NSMapTable. When you create a header, add the header as the weakly held key, with the indexPath object. If we reuse the header we'll update the indexPath.
When you need to update the headers, you can now enumerate the objects/keys from the NSMapTable as needed.
#interface YourCVController ()
#property (nonatomic, strong) NSMapTable *sectionHeaders;
#end
#implementation YourCVContoller
- (void)viewDidLoad {
[super viewDidLoad];
// This will weakly hold on to the KEYS and strongly hold on to the OBJECTS
// keys == HeaderView, object == indexPath
self.sectionHeaders = [NSMapTable weakToStrongObjectsMapTable];
}
// Creating a Header. Shove it into our map so we can update on the fly
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PresentationSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"presentationHeader" forIndexPath:indexPath];
// Shove data into header here
...
// Use header as our weak key. If it goes away we don't care about it
// Set indexPath as object so we can easily find our indexPath if we need it
[self.sectionHeaders setObject:indexPath forKey:header];
return header;
}
// Update Received, need to update our headers
- (void) updateHeaders {
NSEnumerator *enumerator = self.sectionHeaders.keyEnumerator;
PresentationSectionHeader *header = nil;
while ((header = enumerator.nextObject)) {
// Update the header as needed here
NSIndexPath *indexPath = [self.sectionHeaders objectForKey:header];
}
}
#end
This question is very old but a simple way to do it is to just set a delay that covers the time your view is animating and disabling the animation while you update the view...usually a delete or insert takes about .35 seconds so just do:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
My problem arose when frame sizes for the supplementary views changed upon invalidating the layout. It appeared that the supplementary views were not refreshing. It turns out they were, but I was building the UICollectionReusableView objects programmatically, and I was not removing the old UILabel subviews. So when the collection view dequeued each header view, the UILabels would pile up, causing erratic appearance.
The solution was to build each UICollectionReusableView completely inside the viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath method, starting by a) removing all subviews from the dequeued cell, then b) getting the frame size from the item's layout attributes to allow adding the new subviews.
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
yourClass *header = (yourClass *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"identifier" forIndexPath:indexPath];
[[header viewWithTag:1] removeFromSuperview]; // remove additional subviews as required
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForSupplementaryElementOfKind:kind atIndexPath:indexPath];
CGRect frame = attributes.frame;
UILabel *label = [[UILabel alloc] initWithFrame: // CGRectMake based on header frame
label.tag = 1;
[header addSubview:label];
// configure label
return header;
}
I have got a Perfect solution:
let footerView = self.collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionFooter)
Now you can access all subview of footerView by using:
footerView[0].subviews[0]
If you have label in your footerView then :
let label: UILabel = footerView[0].subviews[0] as? UILabel ?? UILabel()
Final Step:
label.text = "Successfully Updated Footer."
if let footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) as? LoadingFooterCell {
footerView.isLoading = .loading
}
I want to implement pull-down-to-refresh in a UICollectionViewController under iOS 6. This was easy to achieve with a UITableViewController, like so:
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(startRefresh:)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
The above implements a nice liquid-drop animation as part of a native widget.
As UICollectionViewController is a "more evolved" UITableViewController one would expect somewhat of a parity of features, but I can't find a reference anywhere to a built-in way to implement this.
Is there a simple way to do this that I'm overlooking?
Can UIRefreshControl be used somehow with UICollectionViewController despite the header and docs both stating that it's meant to be used with a table view?
The answers to both (1) and (2) are yes.
Simply add a UIRefreshControl instance as a subview of .collectionView and it just works.
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(startRefresh:)
forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:refreshControl];
That's it! I wish this had been mentioned in the documentation somewhere, even though sometimes a simple experiment does the trick.
EDIT: this solution won't work if the collection is not big enough to have an active scrollbar. If you add this statement,
self.collectionView.alwaysBounceVertical = YES;
then everything works perfectly. This fix taken from another post on the same topic (referenced in a comment in the other posted answer).
I was looking for the same solution, but in Swift. Based on the above answer, I have done the following:
let refreshCtrl = UIRefreshControl()
...
refreshCtrl.addTarget(self, action: "startRefresh", forControlEvents: .ValueChanged)
collectionView?.addSubview(refreshCtrl)
Not forgetting to:
refreshCtrl.endRefreshing()
I was using Storyboard and setting self.collectionView.alwaysBounceVertical = YES; did not work. Selecting the Bounces and Bounces Vertically does the job for me.
The refreshControl property has now been added to UIScrollView as of iOS 10 so you can set the refresh control directly on collection views.
https://developer.apple.com/reference/uikit/uiscrollview/2127691-refreshcontrol
UIRefreshControl *refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:#selector(refreshControlAction:) forControlEvents:UIControlEventValueChanged];
self.collectionView.refreshControl = refreshControl;
mjh's answer is correct.
I ran into the issue where if the the collectionView.contentSize was not larger then the collectionView.frame.size, you can not get the collectionView to scroll. You can not set the contentSize property either (at least I couldn't).
If it can't scroll, it won't let you do the pull to refresh.
My solution was to subclass UICollectionViewFlowLayout and overide the method:
- (CGSize)collectionViewContentSize
{
CGFloat height = [super collectionViewContentSize].height;
// Always returns a contentSize larger then frame so it can scroll and UIRefreshControl will work
if (height < self.collectionView.bounds.size.height) {
height = self.collectionView.bounds.size.height + 1;
}
return CGSizeMake([super collectionViewContentSize].width, height);
}