Explorar el Código

Add file browser to FLEX.

Based on existing code in Flipboard written by @edog1203 with contributions from others. The file browser allows users to explore the file system within the application’s sandbox and view text files, images, video, etc.
Ryan Olson hace 12 años
padre
commit
9f4d90d757

+ 15 - 0
Classes/Global State Explorers/FLEXFileBrowserTableViewController.h

@@ -0,0 +1,15 @@
+//
+//  FLEXFileBrowserTableViewController.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/9/14.
+//  Based on previous work by Evan Doll
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXFileBrowserTableViewController : UITableViewController
+
+- (id)initWithPath:(NSString *)path;
+
+@end

+ 168 - 0
Classes/Global State Explorers/FLEXFileBrowserTableViewController.m

@@ -0,0 +1,168 @@
+//
+//  FLEXFileBrowserTableViewController.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/9/14.
+//
+//
+
+#import "FLEXFileBrowserTableViewController.h"
+#import "FLEXUtility.h"
+#import "FLEXWebViewController.h"
+
+@interface FLEXFileBrowserTableViewController ()
+
+@property (nonatomic, copy) NSString *path;
+@property (nonatomic, copy) NSArray *childPaths;
+@property (nonatomic, strong) NSNumber *recursiveSize;
+
+@end
+
+@implementation FLEXFileBrowserTableViewController
+
+- (id)initWithStyle:(UITableViewStyle)style
+{
+    return [self initWithPath:NSHomeDirectory()];
+}
+
+- (id)initWithPath:(NSString *)path
+{
+    self = [super initWithStyle:UITableViewStyleGrouped];
+    if (self) {
+        self.path = path;
+        self.title = [path lastPathComponent];
+        
+        FLEXFileBrowserTableViewController *__weak weakSelf = self;
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+            NSFileManager *fileManager = [[NSFileManager alloc] init];
+            NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
+            uint64_t totalSize = [attributes fileSize];
+            
+            for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
+                attributes = [fileManager attributesOfItemAtPath:[path stringByAppendingPathComponent:fileName] error:NULL];
+                totalSize += [attributes fileSize];
+                
+                // Bail if the interested view controller has gone away.
+                if (!weakSelf) {
+                    return;
+                }
+            }
+            
+            dispatch_async(dispatch_get_main_queue(), ^{
+                weakSelf.recursiveSize = @(totalSize);
+                [weakSelf.tableView reloadData];
+            });
+        });
+        
+        self.childPaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
+    }
+    return self;
+}
+
+- (BOOL)prefersStatusBarHidden
+{
+    return YES;
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    return [self.childPaths count];
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+    NSString *sizeString = nil;
+    if (!self.recursiveSize) {
+        sizeString = @"Computing size…";
+    } else {
+        sizeString = [NSByteCountFormatter stringFromByteCount:[self.recursiveSize longLongValue] countStyle:NSByteCountFormatterCountStyleFile];
+    }
+    
+    return [NSString stringWithFormat:@"%lu files (%@)", (unsigned long)[self.childPaths count], sizeString];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
+    NSString *fullPath = [self.path stringByAppendingPathComponent:subpath];
+    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
+    BOOL isDirectory = [[attributes fileType] isEqual:NSFileTypeDirectory];
+    NSString *subtitle = nil;
+    if (isDirectory) {
+        NSUInteger count = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:fullPath error:NULL] count];
+        subtitle = [NSString stringWithFormat:@"%lu file%@", (unsigned long)count, (count == 1 ? @"" : @"s")];
+    } else {
+        NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleFile];
+        subtitle = [NSString stringWithFormat:@"%@ - %@", sizeString, [attributes fileModificationDate]];
+    }
+    
+    static NSString *cellIdentifier = @"Cell";
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+    if (!cell) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
+        cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
+        cell.detailTextLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
+        cell.detailTextLabel.textColor = [UIColor grayColor];
+        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+    }
+    cell.textLabel.text = [subpath lastPathComponent];
+    cell.detailTextLabel.text = subtitle;
+    
+    return cell;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
+    NSString *fullPath = [self.path stringByAppendingPathComponent:subpath];
+    BOOL isDirectory = NO;
+    BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
+    if (stillExists) {
+        UIViewController *drillInViewController = nil;
+        if (isDirectory) {
+            drillInViewController = [[[self class] alloc] initWithPath:fullPath];
+        } else {
+            // Special case keyed archives, json, and plists to get more readable data.
+            NSString *prettyString = nil;
+            if ([[subpath pathExtension] isEqual:@"archive"]) {
+                prettyString = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] description];
+            } else if ([[subpath pathExtension] isEqualToString:@"json"]) {
+                NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
+                id jsonObject = [NSJSONSerialization JSONObjectWithData:fileData options:0 error:NULL];
+                prettyString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL] encoding:NSUTF8StringEncoding];
+            } else if ([[subpath pathExtension] isEqualToString:@"plist"]) {
+                NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
+                prettyString = [[NSPropertyListSerialization propertyListWithData:fileData options:0 format:NULL error:NULL] description];
+            }
+            
+            if ([prettyString length] > 0) {
+                drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
+            } else if ([FLEXWebViewController supportsPathExtension:[subpath pathExtension]]) {
+                drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
+            } else {
+                NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
+                if ([fileString length] > 0) {
+                    drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
+                }
+            }
+        }
+        
+        if (drillInViewController) {
+            drillInViewController.title = [subpath lastPathComponent];
+            [self.navigationController pushViewController:drillInViewController animated:YES];
+        } else {
+            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+        }
+    } else {
+        [[[UIAlertView alloc] initWithTitle:@"File Removed" message:@"The file at the specified path no longer exists." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
+    }
+}
+
+@end

+ 18 - 0
Classes/Global State Explorers/FLEXWebViewController.h

@@ -0,0 +1,18 @@
+//
+//  FLEXWebViewController.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/10/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXWebViewController : UIViewController
+
+- (id)initWithURL:(NSURL *)url;
+- (id)initWithText:(NSString *)text;
+
++ (BOOL)supportsPathExtension:(NSString *)extension;
+
+@end

+ 123 - 0
Classes/Global State Explorers/FLEXWebViewController.m

@@ -0,0 +1,123 @@
+//
+//  FLEXWebViewController.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/10/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXWebViewController.h"
+#import "FLEXUtility.h"
+
+@interface FLEXWebViewController () <UIWebViewDelegate>
+
+@property (nonatomic, strong) UIWebView *webView;
+@property (nonatomic, strong) NSString *originalText;
+
+@end
+
+@implementation FLEXWebViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        self.webView = [[UIWebView alloc] init];
+        self.webView.delegate = self;
+        self.webView.dataDetectorTypes = UIDataDetectorTypeLink;
+        self.webView.scalesPageToFit = YES;
+    }
+    return self;
+}
+
+- (id)initWithText:(NSString *)text
+{
+    self = [self initWithNibName:nil bundle:nil];
+    if (self) {
+        self.originalText = text;
+        NSString *htmlString = [NSString stringWithFormat:@"<pre>%@</pre>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
+        [self.webView loadHTMLString:htmlString baseURL:nil];
+    }
+    return self;
+}
+
+- (id)initWithURL:(NSURL *)url
+{
+    self = [self initWithNibName:nil bundle:nil];
+    if (self) {
+        NSURLRequest *request = [NSURLRequest requestWithURL:url];
+        [self.webView loadRequest:request];
+    }
+    return self;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    
+    [self.view addSubview:self.webView];
+    self.webView.frame = self.view.bounds;
+    self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    
+    if ([self.originalText length] > 0) {
+        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy to Clipboard" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonTapped:)];
+    }
+}
+
+- (BOOL)prefersStatusBarHidden
+{
+    return YES;
+}
+
+- (void)copyButtonTapped:(id)sender
+{
+    [[UIPasteboard generalPasteboard] setString:self.originalText];
+}
+
+
+#pragma mark - UIWebView Delegate
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    BOOL shouldStart = NO;
+    if (navigationType == UIWebViewNavigationTypeOther) {
+        // Allow the initial load
+        shouldStart = YES;
+    } else {
+        // For clicked links, push another web view controller onto the navigation stack so that hitting the back button works as expected.
+        // Don't allow the current web view do handle the navigation.
+        FLEXWebViewController *webVC = [[[self class] alloc] initWithURL:[request URL]];
+        [self.navigationController pushViewController:webVC animated:YES];
+    }
+    return shouldStart;
+}
+
+
+#pragma mark - Class Helpers
+
++ (BOOL)supportsPathExtension:(NSString *)extension
+{
+    BOOL supported = NO;
+    NSSet *supportedExtensions = [self webViewSupportedPathExtensions];
+    if ([supportedExtensions containsObject:[extension lowercaseString]]) {
+        supported = YES;
+    }
+    return supported;
+}
+
++ (NSSet *)webViewSupportedPathExtensions
+{
+    static NSSet *pathExtenstions = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        // Note that this is not exhaustive, but all these extensions should work well in the web view.
+        // See https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7
+        pathExtenstions = [NSSet setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
+                                                @"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
+                                                @"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
+        
+    });
+    return pathExtenstions;
+}
+
+@end