I'm new to the site and probably even newer to app development, however i'm learning slowly.. lol
So i hit my first big snag, I've researched all over on the web and can't find what I'm looking for, so here we go.
I'm creating an app that calls upon some user inputted files that can and do change on occasion. I've added the UIFileSharing option for this so users can upload files via iTunes.
There are 3 different types of files that will need to be used, a .opt, a .pkg, and a .txt.
Is there some way i can take the files from the directory, read them, and based off the file extension pull them and use them in a UIPickerView wheel? I'm really new at this so please forgive me when i ask you to be specific.
My assumption is to do this in a few steps, first read the files and sort them and place them in an array based on the extension, then use said array to populate the picker, and also to get the count for number of rows etc..
I guess the second two parts are pretty simple to figure out, just setting up a picker to use an array, i just need to know if its possible to build that array based on the user loaded files.
thanks in advance,
Chuck
You'll need to use the UIDocumentInteractionController class.
Simple Example
UIDocumentInteractionController * controller;
NSURL *fileURL=[NSURL fileURLWithPath:[self getFilePath]]
controller = [ UIDocumentInteractionController interactionControllerWithURL: fileURL ];
// How to get File From the Document Directory
-(NSString *)getFilePath{
NSString *documentPath= [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
return [documentPath stringByAppendingPathComponent:#"Your file name"]
}
I solved it guys, thanks for all the help.. Code is below, for anyone interested. Don't forget to define your arrays!
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *bundleDirectory = [fileManager contentsOfDirectoryAtPath:documentsPath error:nil];
NSPredicate *Option = [NSPredicate predicateWithFormat:#"self ENDSWITH '.opt'"];
_OptionArray = [bundleDirectory filteredArrayUsingPredicate:Option];
_OptionPickerData = [_OptionArray copy];
I want to add a button that show/hides the popover, similarly to the one of the DropBox app.
(In both landscape & portrait)
I have tried many solutions, but at this stage I don't even want to muddy the water with my attempts. If you've done this, or know how to do this, please send me in the right direction!
Thanks!
it appears it's quite simple.
Set some object to be delegate of splitViewController. In my case (I create all viewcontrollers programatically) that was appdelegate.
UISplitViewController* splitViewController = [[UISplitViewController alloc] init];
[splitViewController setViewControllers:#[navigationViewController1, navigationViewController2]];
splitViewController.delegate = self;
Implement delegate method to hide master in portrait orientation:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation {
return UIInterfaceOrientationIsPortrait(orientation);
}
Actually add barButtonItem:
-(void)splitViewController:(UISplitViewController *)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)pc {
UINavigationController* slaveNavigationViewController = svc.viewControllers[1];
UIViewController* slaveViewController = slaveNavigationViewController.viewControllers[0];
[barButtonItem setTitle:#"Your master title"];
slaveViewController.navigationItem.leftBarButtonItem = barButtonItem;
}
In this method, you get barButtonItem which you customize and add to slaveViewController.
And last one, remove the button in landscape orientation:
- (void)splitViewController:(UISplitViewController *)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
UINavigationController* slaveNavigationViewController = svc.viewControllers[1];
UIViewController* slaveViewController = slaveNavigationViewController.viewControllers[0];
[barButtonItem setTitle:#"Drops"];
slaveViewController.navigationItem.leftBarButtonItem = nil;
}
That's it.
There's a simpler, undocumented way to do it. For an existing UIButton:
[button addTarget: theSplitViewController action: #selector(toggleMasterVisible:) forControlEvents:UIControlEventTouchUpInside];
This target/action are the same as for the barButtonItem sent in the willHideViewController function.
I want to enter text into a text field and based on the text (if it says "No") I want the alertview to pop up. I've created a UITextField in interface builder and attached it to the UITextField delegate in the files owner so that's all taken care of. But when I enter text into the text field, specifically no, and click out of the textfield or remove the keyboard, no alertview pops up. I have created an entire method for this within the viewcontroller.m file. Here is the code for that method.
-(void)engageAlert {
if (myTextField.text == #"No") {
theAlertView = [[UIAlertView alloc] initWithTitle:#"Notice" message:#"MyMessageHere"
delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[theAlertView show];
}
}
Am I missing something here? Nothing happens when I type No into the textfield.
I think the culprit will be the line
myTextField.text == #"No"
Since you're comparing two pointers which will never match.
#"No" = 0x12341234 or whatever address for the static address of the NSString (#"" can be thought of as short hand for the compiler creating the NSString object)
What you will want is something like
if( [myTextField.text isEqualToString:#"No"])
Also check you're not going to leak memory with theAlertView as I can't see where you're freeing that in the snippet.
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'm trying to display the content of a simple plist (xml) file in an outlineview.
Once I have the file data in either an NSXMLDocument or an NSDictionary, is it possible to just use this existing structure to populate the TreeController? All the code examples I can find parse through and reconstruct all the nodes and contents. Isn't this already established in the NSXMLDocument?
thanks
rob
Bindings make this really easy.
You can use a NSTreeController combined with an NSOutlineView and very little code if you use standard bindings.
To make the NSXML objects in the sample application work together with the NSTreeController object, you simply have to add a couple methods to the NSXMLNode class through a category.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NSXML_Concepts/Articles/UsingTreeControllers.html
#import "NSXMLNode+NSXMLNodeAdditions.h"
#implementation NSXMLNode (NSXMLNodeAdditions)
- (NSString *)displayName {
NSString *displayName = [self name];
if (!displayName) {
displayName = [self stringValue];
}
return displayName;
}
- (BOOL)isLeaf {
return [self kind] == NSXMLTextKind ? YES : NO;
}
#end
here are screenshots of the relevant settings for both the NSTreeContoller
and NSOutlineView's TableColumn