[Examples: ASDKgram] Initial commit of a new sample app - a 500px photo feed / social app using layout specs and nodes.
BIN
examples/ASDKgram/Default-568h@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
examples/ASDKgram/Default-667h@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
examples/ASDKgram/Default-736h@3x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
3
examples/ASDKgram/Podfile
Normal file
@@ -0,0 +1,3 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '8.0'
|
||||
pod 'AsyncDisplayKit', :path => '../..'
|
||||
505
examples/ASDKgram/Sample.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,505 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; };
|
||||
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; };
|
||||
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; };
|
||||
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; };
|
||||
767A5F111CAA3BFE004CDA8D /* tabBarIcons in Resources */ = {isa = PBXBuildFile; fileRef = 767A5F101CAA3BFE004CDA8D /* tabBarIcons */; };
|
||||
767A5F131CAA3C66004CDA8D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 767A5F121CAA3C66004CDA8D /* Assets.xcassets */; };
|
||||
768843801CAA37EF00D8629E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843681CAA37EF00D8629E /* AppDelegate.m */; };
|
||||
768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843691CAA37EF00D8629E /* CommentFeedModel.m */; };
|
||||
768843821CAA37EF00D8629E /* CommentModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436A1CAA37EF00D8629E /* CommentModel.m */; };
|
||||
768843831CAA37EF00D8629E /* CommentsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436B1CAA37EF00D8629E /* CommentsNode.m */; };
|
||||
768843841CAA37EF00D8629E /* CommentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436C1CAA37EF00D8629E /* CommentView.m */; };
|
||||
768843851CAA37EF00D8629E /* ImageURLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436D1CAA37EF00D8629E /* ImageURLModel.m */; };
|
||||
768843861CAA37EF00D8629E /* LikersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436E1CAA37EF00D8629E /* LikersViewController.m */; };
|
||||
768843871CAA37EF00D8629E /* LocationCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688436F1CAA37EF00D8629E /* LocationCollectionViewController.m */; };
|
||||
768843881CAA37EF00D8629E /* LocationModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843701CAA37EF00D8629E /* LocationModel.m */; };
|
||||
768843891CAA37EF00D8629E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843711CAA37EF00D8629E /* main.m */; };
|
||||
7688438A1CAA37EF00D8629E /* MoreInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843721CAA37EF00D8629E /* MoreInfoViewController.m */; };
|
||||
7688438B1CAA37EF00D8629E /* PhotoCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843731CAA37EF00D8629E /* PhotoCellNode.m */; };
|
||||
7688438C1CAA37EF00D8629E /* PhotoCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */; };
|
||||
7688438D1CAA37EF00D8629E /* PhotoFeedModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843751CAA37EF00D8629E /* PhotoFeedModel.m */; };
|
||||
7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */; };
|
||||
7688438F1CAA37EF00D8629E /* PhotoMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843771CAA37EF00D8629E /* PhotoMapViewController.m */; };
|
||||
768843901CAA37EF00D8629E /* PhotoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843781CAA37EF00D8629E /* PhotoModel.m */; };
|
||||
768843911CAA37EF00D8629E /* PhotoTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 768843791CAA37EF00D8629E /* PhotoTableViewCell.m */; };
|
||||
768843921CAA37EF00D8629E /* PhotoTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437A1CAA37EF00D8629E /* PhotoTableViewController.m */; };
|
||||
768843931CAA37EF00D8629E /* UserModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437B1CAA37EF00D8629E /* UserModel.m */; };
|
||||
768843941CAA37EF00D8629E /* UserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437C1CAA37EF00D8629E /* UserProfileViewController.m */; };
|
||||
768843951CAA37EF00D8629E /* UserRowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437D1CAA37EF00D8629E /* UserRowView.m */; };
|
||||
768843961CAA37EF00D8629E /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7688437E1CAA37EF00D8629E /* Utilities.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
|
||||
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
|
||||
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; };
|
||||
6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; };
|
||||
767A5F101CAA3BFE004CDA8D /* tabBarIcons */ = {isa = PBXFileReference; lastKnownFileType = folder; path = tabBarIcons; sourceTree = "<group>"; };
|
||||
767A5F121CAA3C66004CDA8D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
768843511CAA37EF00D8629E /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
768843521CAA37EF00D8629E /* CommentFeedModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentFeedModel.h; sourceTree = "<group>"; };
|
||||
768843531CAA37EF00D8629E /* CommentModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentModel.h; sourceTree = "<group>"; };
|
||||
768843541CAA37EF00D8629E /* CommentsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsNode.h; sourceTree = "<group>"; };
|
||||
768843551CAA37EF00D8629E /* CommentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentView.h; sourceTree = "<group>"; };
|
||||
768843561CAA37EF00D8629E /* ImageURLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageURLModel.h; sourceTree = "<group>"; };
|
||||
768843571CAA37EF00D8629E /* LikersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LikersViewController.h; sourceTree = "<group>"; };
|
||||
768843581CAA37EF00D8629E /* LocationCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationCollectionViewController.h; sourceTree = "<group>"; };
|
||||
768843591CAA37EF00D8629E /* LocationModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationModel.h; sourceTree = "<group>"; };
|
||||
7688435A1CAA37EF00D8629E /* MoreInfoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoreInfoViewController.h; sourceTree = "<group>"; };
|
||||
7688435B1CAA37EF00D8629E /* PhotoCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoCellNode.h; sourceTree = "<group>"; };
|
||||
7688435C1CAA37EF00D8629E /* PhotoCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
7688435D1CAA37EF00D8629E /* PhotoFeedModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedModel.h; sourceTree = "<group>"; };
|
||||
7688435E1CAA37EF00D8629E /* PhotoFeedNodeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedNodeController.h; sourceTree = "<group>"; };
|
||||
7688435F1CAA37EF00D8629E /* PhotoMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoMapViewController.h; sourceTree = "<group>"; };
|
||||
768843601CAA37EF00D8629E /* PhotoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoModel.h; sourceTree = "<group>"; };
|
||||
768843611CAA37EF00D8629E /* PhotoTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoTableViewCell.h; sourceTree = "<group>"; };
|
||||
768843621CAA37EF00D8629E /* PhotoTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoTableViewController.h; sourceTree = "<group>"; };
|
||||
768843631CAA37EF00D8629E /* UserModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserModel.h; sourceTree = "<group>"; };
|
||||
768843641CAA37EF00D8629E /* UserProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserProfileViewController.h; sourceTree = "<group>"; };
|
||||
768843651CAA37EF00D8629E /* UserRowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserRowView.h; sourceTree = "<group>"; };
|
||||
768843661CAA37EF00D8629E /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = "<group>"; };
|
||||
768843671CAA37EF00D8629E /* Flickrgram.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Flickrgram.pch; sourceTree = "<group>"; };
|
||||
768843681CAA37EF00D8629E /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
768843691CAA37EF00D8629E /* CommentFeedModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentFeedModel.m; sourceTree = "<group>"; };
|
||||
7688436A1CAA37EF00D8629E /* CommentModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentModel.m; sourceTree = "<group>"; };
|
||||
7688436B1CAA37EF00D8629E /* CommentsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsNode.m; sourceTree = "<group>"; };
|
||||
7688436C1CAA37EF00D8629E /* CommentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentView.m; sourceTree = "<group>"; };
|
||||
7688436D1CAA37EF00D8629E /* ImageURLModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageURLModel.m; sourceTree = "<group>"; };
|
||||
7688436E1CAA37EF00D8629E /* LikersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LikersViewController.m; sourceTree = "<group>"; };
|
||||
7688436F1CAA37EF00D8629E /* LocationCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationCollectionViewController.m; sourceTree = "<group>"; };
|
||||
768843701CAA37EF00D8629E /* LocationModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationModel.m; sourceTree = "<group>"; };
|
||||
768843711CAA37EF00D8629E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
768843721CAA37EF00D8629E /* MoreInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MoreInfoViewController.m; sourceTree = "<group>"; };
|
||||
768843731CAA37EF00D8629E /* PhotoCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoCellNode.m; sourceTree = "<group>"; };
|
||||
768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoCollectionViewCell.m; sourceTree = "<group>"; };
|
||||
768843751CAA37EF00D8629E /* PhotoFeedModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedModel.m; sourceTree = "<group>"; };
|
||||
768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedNodeController.m; sourceTree = "<group>"; };
|
||||
768843771CAA37EF00D8629E /* PhotoMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoMapViewController.m; sourceTree = "<group>"; };
|
||||
768843781CAA37EF00D8629E /* PhotoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoModel.m; sourceTree = "<group>"; };
|
||||
768843791CAA37EF00D8629E /* PhotoTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoTableViewCell.m; sourceTree = "<group>"; };
|
||||
7688437A1CAA37EF00D8629E /* PhotoTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoTableViewController.m; sourceTree = "<group>"; };
|
||||
7688437B1CAA37EF00D8629E /* UserModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserModel.m; sourceTree = "<group>"; };
|
||||
7688437C1CAA37EF00D8629E /* UserProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserProfileViewController.m; sourceTree = "<group>"; };
|
||||
7688437D1CAA37EF00D8629E /* UserRowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserRowView.m; sourceTree = "<group>"; };
|
||||
7688437E1CAA37EF00D8629E /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = "<group>"; };
|
||||
7688437F1CAA37EF00D8629E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
05E2127E19D4DB510098F589 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
05E2127819D4DB510098F589 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05E2128319D4DB510098F589 /* Sample */,
|
||||
05E2128219D4DB510098F589 /* Products */,
|
||||
1A943BF0259746F18D6E423F /* Frameworks */,
|
||||
1AE410B73DA5C3BD087ACDD7 /* Pods */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
05E2128219D4DB510098F589 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05E2128119D4DB510098F589 /* Sample.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
05E2128319D4DB510098F589 /* Sample */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
767A5F121CAA3C66004CDA8D /* Assets.xcassets */,
|
||||
768843511CAA37EF00D8629E /* AppDelegate.h */,
|
||||
768843521CAA37EF00D8629E /* CommentFeedModel.h */,
|
||||
768843531CAA37EF00D8629E /* CommentModel.h */,
|
||||
768843541CAA37EF00D8629E /* CommentsNode.h */,
|
||||
768843551CAA37EF00D8629E /* CommentView.h */,
|
||||
768843561CAA37EF00D8629E /* ImageURLModel.h */,
|
||||
768843571CAA37EF00D8629E /* LikersViewController.h */,
|
||||
768843581CAA37EF00D8629E /* LocationCollectionViewController.h */,
|
||||
768843591CAA37EF00D8629E /* LocationModel.h */,
|
||||
7688435A1CAA37EF00D8629E /* MoreInfoViewController.h */,
|
||||
7688435B1CAA37EF00D8629E /* PhotoCellNode.h */,
|
||||
7688435C1CAA37EF00D8629E /* PhotoCollectionViewCell.h */,
|
||||
7688435D1CAA37EF00D8629E /* PhotoFeedModel.h */,
|
||||
7688435E1CAA37EF00D8629E /* PhotoFeedNodeController.h */,
|
||||
7688435F1CAA37EF00D8629E /* PhotoMapViewController.h */,
|
||||
768843601CAA37EF00D8629E /* PhotoModel.h */,
|
||||
768843611CAA37EF00D8629E /* PhotoTableViewCell.h */,
|
||||
768843621CAA37EF00D8629E /* PhotoTableViewController.h */,
|
||||
768843631CAA37EF00D8629E /* UserModel.h */,
|
||||
768843641CAA37EF00D8629E /* UserProfileViewController.h */,
|
||||
768843651CAA37EF00D8629E /* UserRowView.h */,
|
||||
768843661CAA37EF00D8629E /* Utilities.h */,
|
||||
768843671CAA37EF00D8629E /* Flickrgram.pch */,
|
||||
768843681CAA37EF00D8629E /* AppDelegate.m */,
|
||||
768843691CAA37EF00D8629E /* CommentFeedModel.m */,
|
||||
7688436A1CAA37EF00D8629E /* CommentModel.m */,
|
||||
7688436B1CAA37EF00D8629E /* CommentsNode.m */,
|
||||
7688436C1CAA37EF00D8629E /* CommentView.m */,
|
||||
7688436D1CAA37EF00D8629E /* ImageURLModel.m */,
|
||||
7688436E1CAA37EF00D8629E /* LikersViewController.m */,
|
||||
7688436F1CAA37EF00D8629E /* LocationCollectionViewController.m */,
|
||||
768843701CAA37EF00D8629E /* LocationModel.m */,
|
||||
768843711CAA37EF00D8629E /* main.m */,
|
||||
768843721CAA37EF00D8629E /* MoreInfoViewController.m */,
|
||||
768843731CAA37EF00D8629E /* PhotoCellNode.m */,
|
||||
768843741CAA37EF00D8629E /* PhotoCollectionViewCell.m */,
|
||||
768843751CAA37EF00D8629E /* PhotoFeedModel.m */,
|
||||
768843761CAA37EF00D8629E /* PhotoFeedNodeController.m */,
|
||||
768843771CAA37EF00D8629E /* PhotoMapViewController.m */,
|
||||
768843781CAA37EF00D8629E /* PhotoModel.m */,
|
||||
768843791CAA37EF00D8629E /* PhotoTableViewCell.m */,
|
||||
7688437A1CAA37EF00D8629E /* PhotoTableViewController.m */,
|
||||
7688437B1CAA37EF00D8629E /* UserModel.m */,
|
||||
7688437C1CAA37EF00D8629E /* UserProfileViewController.m */,
|
||||
7688437D1CAA37EF00D8629E /* UserRowView.m */,
|
||||
7688437E1CAA37EF00D8629E /* Utilities.m */,
|
||||
7688437F1CAA37EF00D8629E /* Info.plist */,
|
||||
05E2128419D4DB510098F589 /* Supporting Files */,
|
||||
);
|
||||
path = Sample;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
05E2128419D4DB510098F589 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
767A5F101CAA3BFE004CDA8D /* tabBarIcons */,
|
||||
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */,
|
||||
6C2C82AA19EE274300767484 /* Default-667h@2x.png */,
|
||||
6C2C82AB19EE274300767484 /* Default-736h@3x.png */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1A943BF0259746F18D6E423F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1AE410B73DA5C3BD087ACDD7 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */,
|
||||
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
05E2128019D4DB510098F589 /* Sample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */;
|
||||
buildPhases = (
|
||||
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */,
|
||||
05E2127D19D4DB510098F589 /* Sources */,
|
||||
05E2127E19D4DB510098F589 /* Frameworks */,
|
||||
05E2127F19D4DB510098F589 /* Resources */,
|
||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
||||
06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Sample;
|
||||
productName = Sample;
|
||||
productReference = 05E2128119D4DB510098F589 /* Sample.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
05E2127919D4DB510098F589 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
05E2128019D4DB510098F589 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
DevelopmentTeam = X7PHB3A2FT;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 05E2127819D4DB510098F589;
|
||||
productRefGroup = 05E2128219D4DB510098F589 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
05E2128019D4DB510098F589 /* Sample */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
05E2127F19D4DB510098F589 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */,
|
||||
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */,
|
||||
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */,
|
||||
767A5F111CAA3BFE004CDA8D /* tabBarIcons in Resources */,
|
||||
767A5F131CAA3C66004CDA8D /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
05E2127D19D4DB510098F589 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
768843891CAA37EF00D8629E /* main.m in Sources */,
|
||||
7688438C1CAA37EF00D8629E /* PhotoCollectionViewCell.m in Sources */,
|
||||
768843921CAA37EF00D8629E /* PhotoTableViewController.m in Sources */,
|
||||
7688438A1CAA37EF00D8629E /* MoreInfoViewController.m in Sources */,
|
||||
768843821CAA37EF00D8629E /* CommentModel.m in Sources */,
|
||||
768843831CAA37EF00D8629E /* CommentsNode.m in Sources */,
|
||||
768843961CAA37EF00D8629E /* Utilities.m in Sources */,
|
||||
768843941CAA37EF00D8629E /* UserProfileViewController.m in Sources */,
|
||||
768843931CAA37EF00D8629E /* UserModel.m in Sources */,
|
||||
768843801CAA37EF00D8629E /* AppDelegate.m in Sources */,
|
||||
768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */,
|
||||
7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */,
|
||||
768843841CAA37EF00D8629E /* CommentView.m in Sources */,
|
||||
768843951CAA37EF00D8629E /* UserRowView.m in Sources */,
|
||||
768843881CAA37EF00D8629E /* LocationModel.m in Sources */,
|
||||
7688438F1CAA37EF00D8629E /* PhotoMapViewController.m in Sources */,
|
||||
768843871CAA37EF00D8629E /* LocationCollectionViewController.m in Sources */,
|
||||
768843901CAA37EF00D8629E /* PhotoModel.m in Sources */,
|
||||
768843861CAA37EF00D8629E /* LikersViewController.m in Sources */,
|
||||
768843911CAA37EF00D8629E /* PhotoTableViewCell.m in Sources */,
|
||||
7688438B1CAA37EF00D8629E /* PhotoCellNode.m in Sources */,
|
||||
7688438D1CAA37EF00D8629E /* PhotoFeedModel.m in Sources */,
|
||||
768843851CAA37EF00D8629E /* ImageURLModel.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
05E212A219D4DB510098F589 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
05E212A319D4DB510098F589 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
05E212A519D4DB510098F589 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
INFOPLIST_FILE = Sample/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
05E212A619D4DB510098F589 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
INFOPLIST_FILE = Sample/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
05E212A219D4DB510098F589 /* Debug */,
|
||||
05E212A319D4DB510098F589 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
05E212A519D4DB510098F589 /* Debug */,
|
||||
05E212A619D4DB510098F589 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 05E2127919D4DB510098F589 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "05E2128019D4DB510098F589"
|
||||
BuildableName = "Sample.app"
|
||||
BlueprintName = "Sample"
|
||||
ReferencedContainer = "container:Sample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "05E2128019D4DB510098F589"
|
||||
BuildableName = "Sample.app"
|
||||
BlueprintName = "Sample"
|
||||
ReferencedContainer = "container:Sample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "05E2128019D4DB510098F589"
|
||||
BuildableName = "Sample.app"
|
||||
BlueprintName = "Sample"
|
||||
ReferencedContainer = "container:Sample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "05E2128019D4DB510098F589"
|
||||
BuildableName = "Sample.app"
|
||||
BlueprintName = "Sample"
|
||||
ReferencedContainer = "container:Sample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
16
examples/ASDKgram/Sample/AppDelegate.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/16/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
@end
|
||||
|
||||
87
examples/ASDKgram/Sample/AppDelegate.m
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/16/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "PhotoTableViewController.h"
|
||||
#import "PhotoFeedNodeController.h"
|
||||
#import "MoreInfoViewController.h"
|
||||
#import "Utilities.h"
|
||||
//#import "KMCGeigerCounter.h"
|
||||
#import "AsyncDisplayKit+Debug.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
// Nav Bar appearance
|
||||
[[UINavigationBar appearance] setBarTintColor:[UIColor darkBlueColor]];
|
||||
[[UINavigationBar appearance] setTranslucent:NO];
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName:[UIColor whiteColor]};
|
||||
[[UINavigationBar appearance] setTitleTextAttributes:attributes];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
UITabBarController *tabBarController = [[UITabBarController alloc] init];
|
||||
self.window.rootViewController = tabBarController;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
// create UIKit-powered Home Feed viewController & navController
|
||||
PhotoFeedNodeController *asdkPoweredHomeFeedVC = [[PhotoFeedNodeController alloc] init];
|
||||
UINavigationController *asdkHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:asdkPoweredHomeFeedVC];
|
||||
asdkHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"ASDK" image:[UIImage imageNamed:@"home"] tag:0];
|
||||
|
||||
// create ASDK-powered Home Feed viewController & navController
|
||||
PhotoTableViewController *uikitPoweredHomeFeedVC = [[PhotoTableViewController alloc] init];
|
||||
UINavigationController *uikitHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:uikitPoweredHomeFeedVC];
|
||||
uikitHomeFeedNavCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"UIKit" image:[UIImage imageNamed:@"home"] tag:0];
|
||||
|
||||
// UINavigationController does not forward on preferredStatusBarStyle calls to its child view controllers.
|
||||
// Instead it manages its own state...
|
||||
//http://stackoverflow.com/questions/19022210/preferredstatusbarstyle-isnt-called/19513714#19513714
|
||||
uikitHomeFeedNavCtrl.navigationBar.barStyle = UIBarStyleBlack;
|
||||
asdkHomeFeedNavCtrl.navigationBar.barStyle = UIBarStyleBlack;
|
||||
|
||||
|
||||
// create Stats viewController & navController
|
||||
// MoreInfoViewController *VC = [[MoreInfoViewController alloc] init];
|
||||
// VC.view.backgroundColor = [UIColor whiteColor];
|
||||
// UINavigationController *statsNavController = [[UINavigationController alloc] initWithRootViewController:VC];
|
||||
// statsNavController.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemMore tag:0];
|
||||
|
||||
// configure UITabBarController and add viewControllers
|
||||
tabBarController.viewControllers = @[uikitHomeFeedNavCtrl, asdkHomeFeedNavCtrl];
|
||||
tabBarController.selectedViewController = asdkHomeFeedNavCtrl;
|
||||
|
||||
// [KMCGeigerCounter sharedGeigerCounter].enabled = YES;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
6
examples/ASDKgram/Sample/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
22
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "camera.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "camera@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera.png
vendored
Normal file
|
After Width: | Height: | Size: 483 B |
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/camera.imageset/camera@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 822 B |
22
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "earth.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "earth@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/earth.imageset/earth@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
22
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "home.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "home@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home.png
vendored
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/home.imageset/home@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 572 B |
22
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "profile.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "profile@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile.png
vendored
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
examples/ASDKgram/Sample/Assets.xcassets/Tab Bar Icons/profile.imageset/profile@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 865 B |
21
examples/ASDKgram/Sample/Assets.xcassets/crosshairs.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "crosshair.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/Assets.xcassets/crosshairs.imageset/crosshair.png
vendored
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
27
examples/ASDKgram/Sample/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
27
examples/ASDKgram/Sample/CommentFeedModel.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// CommentFeedModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CommentModel.h"
|
||||
|
||||
@interface CommentFeedModel : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithPhotoID:(NSString *)photoID NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (NSUInteger)numberOfItemsInFeed;
|
||||
- (CommentModel *)objectAtIndex:(NSUInteger)index;
|
||||
|
||||
- (NSUInteger)numberOfCommentsForPhoto;
|
||||
- (BOOL)numberOfCommentsForPhotoExceedsInteger:(NSUInteger)number;
|
||||
- (NSAttributedString *)viewAllCommentsAttributedString;
|
||||
|
||||
- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block;
|
||||
- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block;
|
||||
|
||||
@end
|
||||
218
examples/ASDKgram/Sample/CommentFeedModel.m
Normal file
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// CommentFeedModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CommentFeedModel.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "Utilities.h"
|
||||
|
||||
#define fiveHundredPX_ENDPOINT_HOST @"https://api.500px.com/v1/"
|
||||
#define fiveHundredPX_ENDPOINT_COMMENTS @"photos/4928401/comments"
|
||||
#define fiveHundredPX_ENDPOINT_SEARCH @"photos/search?geo=" //latitude,longitude,radius<units>
|
||||
#define fiveHundredPX_ENDPOINT_USER @"photos?user_id="
|
||||
#define fiveHundredPX_CONSUMER_KEY_PARAM @"&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC"
|
||||
|
||||
@implementation CommentFeedModel
|
||||
{
|
||||
NSMutableArray *_comments; // array of CommentModel objects
|
||||
|
||||
NSString *_photoID;
|
||||
NSString *_urlString;
|
||||
NSUInteger _currentPage;
|
||||
NSUInteger _totalPages;
|
||||
NSUInteger _totalItems;
|
||||
|
||||
BOOL _fetchPageInProgress;
|
||||
BOOL _refreshFeedInProgress;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSMutableArray *)comments
|
||||
{
|
||||
return _comments;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithPhotoID:(NSString *)photoID
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_photoID = photoID;
|
||||
_currentPage = 0;
|
||||
_totalPages = 0;
|
||||
_totalItems = 0;
|
||||
_comments = [[NSMutableArray alloc] init];
|
||||
|
||||
_urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/photos/%@/comments?",photoID];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (NSUInteger)numberOfItemsInFeed
|
||||
{
|
||||
return [_comments count];
|
||||
}
|
||||
|
||||
- (CommentModel *)objectAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [_comments objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfCommentsForPhoto
|
||||
{
|
||||
return _totalItems;
|
||||
}
|
||||
|
||||
- (BOOL)numberOfCommentsForPhotoExceedsInteger:(NSUInteger)number
|
||||
{
|
||||
return (_totalItems > number);
|
||||
}
|
||||
|
||||
- (NSAttributedString *)viewAllCommentsAttributedString
|
||||
{
|
||||
NSString *string = [NSString stringWithFormat:@"View all %@ comments", [NSNumber numberWithUnsignedInteger:_totalItems]];
|
||||
NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string fontSize:14 color:[UIColor lightGrayColor] firstWordColor:nil];
|
||||
return attrString;
|
||||
}
|
||||
|
||||
- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block
|
||||
{
|
||||
// only one fetch at a time
|
||||
if (_fetchPageInProgress) {
|
||||
|
||||
// NSLog(@"Request COMMENTS: FAIL - fetch page already in progress");
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
_fetchPageInProgress = YES;
|
||||
|
||||
// NSLog(@"Request COMMENTS: SUCCESS");
|
||||
[self fetchPageWithCompletionBlock:block];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block
|
||||
{
|
||||
// only one fetch at a time
|
||||
if (_refreshFeedInProgress) {
|
||||
|
||||
// NSLog(@"Request Refresh COMMENTS: FAIL - refresh feed already in progress");
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
_refreshFeedInProgress = YES;
|
||||
_currentPage = 0;
|
||||
|
||||
// FIXME: blow away any other requests in progress
|
||||
|
||||
[self fetchPageWithCompletionBlock:^(NSArray *newPhotos) {
|
||||
if (block) {
|
||||
block(newPhotos);
|
||||
}
|
||||
|
||||
_refreshFeedInProgress = NO;
|
||||
} replaceData:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block
|
||||
{
|
||||
[self fetchPageWithCompletionBlock:block replaceData:NO];
|
||||
}
|
||||
|
||||
- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block replaceData:(BOOL)replaceData
|
||||
{
|
||||
// early return if reached end of pages
|
||||
if (_totalPages) {
|
||||
if (_currentPage == _totalPages) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
NSMutableArray *newComments = [NSMutableArray array];
|
||||
|
||||
@synchronized(self) {
|
||||
|
||||
NSUInteger nextPage = _currentPage + 1;
|
||||
|
||||
NSString *urlAdditions = [NSString stringWithFormat:@"page=%lu", (unsigned long)nextPage];
|
||||
NSURL *url = [NSURL URLWithString:[_urlString stringByAppendingString:urlAdditions]];
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfURL:url];
|
||||
|
||||
if (data) {
|
||||
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
|
||||
|
||||
if ([response isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
_currentPage = [[response valueForKeyPath:@"current_page"] integerValue];
|
||||
_totalPages = [[response valueForKeyPath:@"total_pages"] integerValue];
|
||||
_totalItems = [[response valueForKeyPath:@"total_items"] integerValue];
|
||||
|
||||
NSArray *comments = [response valueForKeyPath:@"comments"];
|
||||
|
||||
// NSLog(@"Request Refresh COMMENTS: SUCCESS %@", comments);
|
||||
|
||||
if ([comments isKindOfClass:[NSArray class]]) {
|
||||
|
||||
NSUInteger numComments = [comments count];
|
||||
if (numComments > 3) {
|
||||
comments = [comments subarrayWithRange:(NSRange){numComments-3, 3}];
|
||||
}
|
||||
|
||||
for (NSDictionary *commentDictionary in comments) {
|
||||
|
||||
if ([response isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
CommentModel *comment = [[CommentModel alloc] initWithDictionary:commentDictionary];
|
||||
|
||||
// addObject: will crash with nil (NSArray, NSSet, NSDictionary, URLWithString - most foundation things)
|
||||
if (comment) {
|
||||
[newComments addObject:comment];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (replaceData) {
|
||||
_comments = [newComments mutableCopy];
|
||||
} else {
|
||||
[_comments addObjectsFromArray:newComments];
|
||||
}
|
||||
|
||||
if (block) {
|
||||
block(newComments);
|
||||
}
|
||||
});
|
||||
|
||||
_fetchPageInProgress = NO;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
27
examples/ASDKgram/Sample/CommentModel.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// CommentModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface CommentModel : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger ID;
|
||||
@property (nonatomic, assign, readonly) NSUInteger commenterID;
|
||||
@property (nonatomic, strong, readonly) NSString *commenterUsername;
|
||||
@property (nonatomic, strong, readonly) NSString *commenterAvatarURL;
|
||||
@property (nonatomic, strong, readonly) NSString *body;
|
||||
@property (nonatomic, strong, readonly) NSString *uploadDateString;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)photoDictionary NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (NSAttributedString *)commentAttributedString;
|
||||
- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size;
|
||||
|
||||
@end
|
||||
56
examples/ASDKgram/Sample/CommentModel.m
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// CommentModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CommentModel.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
@implementation CommentModel
|
||||
{
|
||||
NSDictionary *_dictionaryRepresentation;
|
||||
NSString *_uploadDateRaw;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)photoDictionary
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_dictionaryRepresentation = photoDictionary;
|
||||
|
||||
_ID = [[photoDictionary objectForKey:@"id"] integerValue];
|
||||
_commenterID = [[photoDictionary objectForKey:@"user_id"] integerValue];
|
||||
_commenterUsername = [photoDictionary valueForKeyPath:@"user.username"];
|
||||
_commenterAvatarURL= [photoDictionary valueForKeyPath:@"user.userpic_url"];
|
||||
_body = [photoDictionary objectForKey:@"body"];
|
||||
_uploadDateRaw = [photoDictionary valueForKeyPath:@"created_at"];
|
||||
_uploadDateString = [NSString elapsedTimeStringSinceDate:_uploadDateRaw];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (NSAttributedString *)commentAttributedString
|
||||
{
|
||||
NSString *commentString = [NSString stringWithFormat:@"%@ %@",[_commenterUsername lowercaseString], _body];
|
||||
return [NSAttributedString attributedStringWithString:commentString fontSize:14 color:[UIColor darkGrayColor] firstWordColor:[UIColor darkBlueColor]];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size;
|
||||
{
|
||||
return [NSAttributedString attributedStringWithString:self.uploadDateString
|
||||
fontSize:size
|
||||
color:[UIColor lightGrayColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
18
examples/ASDKgram/Sample/CommentView.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// CommentView.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "CommentFeedModel.h"
|
||||
|
||||
@interface CommentView : UIView
|
||||
|
||||
+ (CGFloat)heightForCommentFeedModel:(CommentFeedModel *)feed withWidth:(CGFloat)width;
|
||||
|
||||
- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed;
|
||||
|
||||
@end
|
||||
143
examples/ASDKgram/Sample/CommentView.m
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// CommentView.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CommentView.h"
|
||||
#import "PhotoFeedModel.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
#define INTER_COMMENT_SPACING 5
|
||||
#define NUM_COMMENTS_TO_SHOW 3
|
||||
|
||||
@implementation CommentView
|
||||
{
|
||||
CommentFeedModel *_commentFeed;
|
||||
NSMutableArray <UILabel *> *_commentLabels;
|
||||
}
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
+ (CGFloat)heightForCommentFeedModel:(CommentFeedModel *)feed withWidth:(CGFloat)width
|
||||
{
|
||||
NSAttributedString *string;
|
||||
CGRect rect;
|
||||
CGFloat height = 0;
|
||||
|
||||
BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW];
|
||||
if (addViewAllCommentsLabel) {
|
||||
string = [feed viewAllCommentsAttributedString];
|
||||
rect = [string boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
context:nil];
|
||||
height += rect.size.height;
|
||||
}
|
||||
|
||||
NSUInteger numCommentsInFeed = [feed numberOfItemsInFeed];
|
||||
|
||||
for (int i = 0; i < numCommentsInFeed; i++) {
|
||||
|
||||
string = [[feed objectAtIndex:i] commentAttributedString];
|
||||
rect = [string boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
context:nil];
|
||||
height += rect.size.height + INTER_COMMENT_SPACING;
|
||||
}
|
||||
|
||||
return roundf(height);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_commentLabels = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.bounds.size;
|
||||
CGRect rect = CGRectMake(0, 0, boundsSize.width, -INTER_COMMENT_SPACING);
|
||||
|
||||
for (UILabel *commentsLabel in _commentLabels) {
|
||||
rect.origin.y += rect.size.height + INTER_COMMENT_SPACING;
|
||||
rect.size = [commentsLabel sizeThatFits:CGSizeMake(boundsSize.width, CGFLOAT_MAX)];
|
||||
commentsLabel.frame = rect;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed
|
||||
{
|
||||
_commentFeed = feed;
|
||||
[self removeCommentLabels];
|
||||
|
||||
if (_commentFeed) {
|
||||
[self createCommentLabels];
|
||||
|
||||
BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW];
|
||||
NSAttributedString *commentLabelString;
|
||||
int labelsIndex = 0;
|
||||
|
||||
if (addViewAllCommentsLabel) {
|
||||
commentLabelString = [_commentFeed viewAllCommentsAttributedString];
|
||||
[[_commentLabels objectAtIndex:labelsIndex] setAttributedText:commentLabelString];
|
||||
labelsIndex++;
|
||||
}
|
||||
|
||||
NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed];
|
||||
|
||||
for (int feedIndex = 0; feedIndex < numCommentsInFeed; feedIndex++) {
|
||||
commentLabelString = [[_commentFeed objectAtIndex:feedIndex] commentAttributedString];
|
||||
[[_commentLabels objectAtIndex:labelsIndex] setAttributedText:commentLabelString];
|
||||
labelsIndex++;
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)removeCommentLabels
|
||||
{
|
||||
for (UILabel *commentLabel in _commentLabels) {
|
||||
[commentLabel removeFromSuperview];
|
||||
}
|
||||
|
||||
[_commentLabels removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)createCommentLabels
|
||||
{
|
||||
BOOL addViewAllCommentsLabel = [_commentFeed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW];
|
||||
NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed];
|
||||
|
||||
NSUInteger numLabelsToAdd = (addViewAllCommentsLabel) ? numCommentsInFeed + 1 : numCommentsInFeed;
|
||||
|
||||
for (NSUInteger i = 0; i < numLabelsToAdd; i++) {
|
||||
|
||||
UILabel *commentLabel = [[UILabel alloc] init];
|
||||
commentLabel.numberOfLines = 3;
|
||||
|
||||
[_commentLabels addObject:commentLabel];
|
||||
[self addSubview:commentLabel];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
16
examples/ASDKgram/Sample/CommentsNode.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// CommentsNode.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/21/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "CommentFeedModel.h"
|
||||
|
||||
@interface CommentsNode : ASTextCellNode
|
||||
|
||||
- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed;
|
||||
|
||||
@end
|
||||
103
examples/ASDKgram/Sample/CommentsNode.m
Normal file
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// CommentsNode.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/21/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CommentsNode.h"
|
||||
|
||||
#define INTER_COMMENT_SPACING 5
|
||||
#define NUM_COMMENTS_TO_SHOW 3
|
||||
|
||||
@implementation CommentsNode
|
||||
{
|
||||
CommentFeedModel *_commentFeed;
|
||||
NSMutableArray <ASTextNode *> *_commentNodes;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_commentNodes = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
verticalStack.spacing = INTER_COMMENT_SPACING;
|
||||
[verticalStack setChildren:_commentNodes];
|
||||
|
||||
return verticalStack;
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (void)updateWithCommentFeedModel:(CommentFeedModel *)feed
|
||||
{
|
||||
_commentFeed = feed;
|
||||
[self removeCommentLabels];
|
||||
|
||||
if (_commentFeed) {
|
||||
[self createCommentLabels];
|
||||
|
||||
BOOL addViewAllCommentsLabel = [feed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW];
|
||||
NSAttributedString *commentLabelString;
|
||||
int labelsIndex = 0;
|
||||
|
||||
if (addViewAllCommentsLabel) {
|
||||
commentLabelString = [_commentFeed viewAllCommentsAttributedString];
|
||||
[[_commentNodes objectAtIndex:labelsIndex] setAttributedString:commentLabelString];
|
||||
labelsIndex++;
|
||||
}
|
||||
|
||||
NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed];
|
||||
|
||||
for (int feedIndex = 0; feedIndex < numCommentsInFeed; feedIndex++) {
|
||||
commentLabelString = [[_commentFeed objectAtIndex:feedIndex] commentAttributedString];
|
||||
[[_commentNodes objectAtIndex:labelsIndex] setAttributedString:commentLabelString];
|
||||
labelsIndex++;
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)removeCommentLabels
|
||||
{
|
||||
for (ASTextNode *commentLabel in _commentNodes) {
|
||||
[commentLabel removeFromSupernode];
|
||||
}
|
||||
|
||||
[_commentNodes removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)createCommentLabels
|
||||
{
|
||||
BOOL addViewAllCommentsLabel = [_commentFeed numberOfCommentsForPhotoExceedsInteger:NUM_COMMENTS_TO_SHOW];
|
||||
NSUInteger numCommentsInFeed = [_commentFeed numberOfItemsInFeed];
|
||||
|
||||
NSUInteger numLabelsToAdd = (addViewAllCommentsLabel) ? numCommentsInFeed + 1 : numCommentsInFeed;
|
||||
|
||||
for (NSUInteger i = 0; i < numLabelsToAdd; i++) {
|
||||
|
||||
ASTextNode *commentLabel = [[ASTextNode alloc] init];
|
||||
commentLabel.maximumNumberOfLines = 3;
|
||||
|
||||
[_commentNodes addObject:commentLabel];
|
||||
[self addSubnode:commentLabel];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
17
examples/ASDKgram/Sample/Flickrgram.pch
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Flickrgram.pch
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Flickrgram_pch
|
||||
#define Flickrgram_pch
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// Include any system framework and library headers here that should be included in all compilation units.
|
||||
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
|
||||
|
||||
#endif /* Flickrgram_pch */
|
||||
16
examples/ASDKgram/Sample/ImageURLModel.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ImageURLModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/11/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface ImageURLModel : NSObject
|
||||
|
||||
+ (NSString *)imageParameterForClosestImageSize:(CGSize)size;
|
||||
|
||||
@end
|
||||
50
examples/ASDKgram/Sample/ImageURLModel.m
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ImageURLModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/11/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ImageURLModel.h"
|
||||
|
||||
@implementation ImageURLModel
|
||||
|
||||
+ (NSString *)imageParameterForClosestImageSize:(CGSize)size
|
||||
{
|
||||
BOOL squareImageRequested = (size.width == size.height) ? YES : NO;
|
||||
|
||||
NSUInteger imageParameterID;
|
||||
|
||||
if (squareImageRequested) {
|
||||
imageParameterID = [self imageParameterForSquareCroppedSize:size];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"&image_size=%lu", (long)imageParameterID];
|
||||
}
|
||||
|
||||
// 500px standard cropped image sizes
|
||||
+ (NSUInteger)imageParameterForSquareCroppedSize:(CGSize)size
|
||||
{
|
||||
NSUInteger imageParameterID;
|
||||
|
||||
if (size.height <= 70) {
|
||||
imageParameterID = 1;
|
||||
} else if (size.height <= 100) {
|
||||
imageParameterID = 100;
|
||||
} else if (size.height <= 140) {
|
||||
imageParameterID = 2;
|
||||
} else if (size.height <= 200) {
|
||||
imageParameterID = 200;
|
||||
} else if (size.height <= 280) {
|
||||
imageParameterID = 3;
|
||||
} else if (size.height <= 400) {
|
||||
imageParameterID = 400;
|
||||
} else {
|
||||
imageParameterID = 600;
|
||||
}
|
||||
|
||||
return imageParameterID;
|
||||
}
|
||||
|
||||
@end
|
||||
47
examples/ASDKgram/Sample/Info.plist
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
18
examples/ASDKgram/Sample/LikersViewController.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// LikersViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/14/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "PhotoModel.h"
|
||||
|
||||
@interface LikersViewController : UIViewController
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
|
||||
- (instancetype)initWithPhoto:(PhotoModel *)photo NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
110
examples/ASDKgram/Sample/LikersViewController.m
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// LikersViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/14/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LikersViewController.h"
|
||||
#import "UserRowView.h"
|
||||
#import "CommentFeedModel.h"
|
||||
|
||||
#define AUTO_TAIL_LOADING_NUM_SCREENFULS 1.0
|
||||
|
||||
@interface LikersViewController () <UITableViewDataSource, UITableViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation LikersViewController
|
||||
{
|
||||
PhotoModel *_photo;
|
||||
UITableView *_tableView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithPhoto:(PhotoModel *)photo
|
||||
{
|
||||
self = [super initWithNibName:nil bundle:nil];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.navigationItem.title = @"LIKERS";
|
||||
|
||||
_photo = photo;
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.allowsSelection = NO;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.delegate = self;
|
||||
|
||||
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"userRowCell"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self.view addSubview:_tableView];
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
self.navigationController.hidesBarsOnSwipe = NO; // FIXME: why won't this work?
|
||||
|
||||
_tableView.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
CGFloat currentOffSetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
|
||||
|
||||
// automatic tail loading
|
||||
CGFloat screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight;
|
||||
if (screenfullsBeforeBottom < AUTO_TAIL_LOADING_NUM_SCREENFULS) {
|
||||
// NSLog(@"AUTOMATIC TAIL LOADING BEGIN");
|
||||
[_photo.commentFeed requestPageWithCompletionBlock:^(NSArray *newComments) { // FIXME: add number of items to load
|
||||
[_tableView reloadData]; //FIXME: insert
|
||||
[self.view setNeedsLayout];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITableViewDataSource methods
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [_photo.commentFeed numberOfItemsInFeed];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:@"userRowCell"];
|
||||
|
||||
if ([[cell.contentView subviews] count] == 0) {
|
||||
UserRowView *userRowView = [[UserRowView alloc] initWithFrame:cell.frame withPhotoFeedModelType:UserRowViewTypeComments];
|
||||
[cell.contentView addSubview:userRowView];
|
||||
}
|
||||
|
||||
CommentModel *comment = [_photo.commentFeed objectAtIndex:indexPath.row];
|
||||
[[[cell.contentView subviews] objectAtIndex:0] updateWithCommentModel:comment];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
@end
|
||||
21
examples/ASDKgram/Sample/LocationCollectionViewController.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// LocationCollectionViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/24/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CLLocation.h>
|
||||
|
||||
@interface LocationCollectionViewController : UICollectionViewController
|
||||
|
||||
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
|
||||
coordinates:(CLLocationCoordinate2D)coordiantes NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout NS_UNAVAILABLE;
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
130
examples/ASDKgram/Sample/LocationCollectionViewController.m
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// LocationCollectionViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/24/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LocationCollectionViewController.h"
|
||||
#import "PhotoCollectionViewCell.h"
|
||||
#import "PhotoFeedModel.h"
|
||||
#import <MapKit/MKMapView.h>
|
||||
#import <MapKit/MKPointAnnotation.h>
|
||||
|
||||
|
||||
#define MAP_HEIGHT_VERTICAL_SCREEN_RATIO 0.3
|
||||
|
||||
@implementation LocationCollectionViewController
|
||||
{
|
||||
CLLocationCoordinate2D _coordinates;
|
||||
MKMapView *_mapView;
|
||||
PhotoFeedModel *_photoFeed;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
|
||||
coordinates:(CLLocationCoordinate2D)coordiantes
|
||||
|
||||
{
|
||||
self = [super initWithCollectionViewLayout:layout];
|
||||
|
||||
if (self) {
|
||||
|
||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
||||
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
||||
CGSize imageSize = CGSizeMake(screenRect.size.width * screenScale / 3.0, screenRect.size.width * screenScale / 3.0);
|
||||
|
||||
_photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypeLocation imageSize:imageSize];
|
||||
[_photoFeed updatePhotoFeedModelTypeLocationCoordinates:coordiantes radiusInMiles:10];
|
||||
[_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos) {
|
||||
[self.collectionView reloadData];
|
||||
} numResultsToReturn:21];
|
||||
|
||||
// set collection view dataSource and delegate
|
||||
self.collectionView.delegate = self;
|
||||
self.collectionView.dataSource = self;
|
||||
self.collectionView.allowsSelection = NO;
|
||||
self.collectionView.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
// register cell class
|
||||
[self.collectionView registerClass:[PhotoCollectionViewCell class] forCellWithReuseIdentifier:@"photo"];
|
||||
[self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView"];
|
||||
|
||||
// configure MKMapView & add as subview
|
||||
_mapView = [[MKMapView alloc] init];
|
||||
_mapView.showsUserLocation = YES;
|
||||
|
||||
// set coordinates
|
||||
_coordinates = coordiantes;
|
||||
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// add annotation for coordinates
|
||||
MKPointAnnotation *photoLocationAnnotation = [[MKPointAnnotation alloc] init];
|
||||
photoLocationAnnotation.coordinate = _coordinates;
|
||||
[_mapView addAnnotation:photoLocationAnnotation];
|
||||
|
||||
// center map on photo pin
|
||||
[_mapView setCenterCoordinate:_coordinates];
|
||||
|
||||
// set map span and region
|
||||
MKCoordinateSpan span = MKCoordinateSpanMake(5, 5);
|
||||
MKCoordinateRegion region = MKCoordinateRegionMake(_coordinates, span);
|
||||
[_mapView setRegion:region animated:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return [_photoFeed numberOfItemsInFeed];
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// dequeue a reusable cell
|
||||
PhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"photo" forIndexPath:indexPath];
|
||||
|
||||
// configure the cell for the appropriate photo
|
||||
[cell updateCellWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UICollectionReusableView *reusableview = nil;
|
||||
|
||||
if (kind == UICollectionElementKindSectionHeader) {
|
||||
UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView" forIndexPath:indexPath];
|
||||
|
||||
if (!headerView) {
|
||||
headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView" forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
_mapView.frame = headerView.frame;
|
||||
[headerView addSubview:_mapView];
|
||||
|
||||
reusableview = headerView;
|
||||
}
|
||||
|
||||
return reusableview;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
23
examples/ASDKgram/Sample/LocationModel.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// LocationModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CoreLocation/CoreLocation.h"
|
||||
|
||||
@interface LocationModel : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) CLLocationCoordinate2D coordinates;
|
||||
@property (nonatomic, strong, readonly) CLPlacemark *placemark;
|
||||
@property (nonatomic, strong, readonly) NSString *locationString;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)reverseGeocodedLocationWithCompletionBlock:(void (^)(LocationModel *))blockName;
|
||||
|
||||
@end
|
||||
177
examples/ASDKgram/Sample/LocationModel.m
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// LocationModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LocationModel.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CLGeocoder.h>
|
||||
|
||||
@implementation LocationModel
|
||||
{
|
||||
BOOL _placemarkFetchInProgress;
|
||||
void (^_placemarkCallbackBlock)(LocationModel *);
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWith500pxPhoto:(NSDictionary *)dictionary
|
||||
{
|
||||
NSNumber *latitude = [dictionary objectForKey:@"latitude"];
|
||||
NSNumber *longitude = [dictionary objectForKey:@"longitude"];
|
||||
|
||||
// early return if location is "<null>"
|
||||
if (![latitude isKindOfClass:[NSNumber class]] || ![longitude isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
// set coordiantes
|
||||
_coordinates = CLLocationCoordinate2DMake([latitude floatValue], [longitude floatValue]);
|
||||
|
||||
// get CLPlacemark with MKReverseGeocoder
|
||||
[self beginReverseGeocodingLocationFromCoordinates];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
// return location placemark if fetched, else set completion block for fetch finish
|
||||
- (void)reverseGeocodedLocationWithCompletionBlock:(void (^)(LocationModel *))blockName
|
||||
{
|
||||
if (_placemark) {
|
||||
|
||||
// call block if placemark already fetched
|
||||
if (blockName) {
|
||||
blockName(self);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// set placemark reverse geocoding completion block
|
||||
_placemarkCallbackBlock = blockName;
|
||||
|
||||
// if fetch not in progress, begin
|
||||
if (!_placemarkFetchInProgress) {
|
||||
|
||||
[self beginReverseGeocodingLocationFromCoordinates];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (nullable NSString *)locationStringFromPhotoLocationDictionary:(NSDictionary *)photoLocationDictionary
|
||||
{
|
||||
// early return if no location info
|
||||
if (!photoLocationDictionary)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *country = [photoLocationDictionary valueForKeyPath:@"country._content"];
|
||||
NSString *county = [photoLocationDictionary valueForKeyPath:@"county._content"];
|
||||
NSString *locality = [photoLocationDictionary valueForKeyPath:@"locality._content"];
|
||||
NSString *neighbourhood = [photoLocationDictionary valueForKeyPath:@"neighbourhood._content"];
|
||||
NSString *region = [photoLocationDictionary valueForKeyPath:@"region._content"];
|
||||
|
||||
NSString *locationString;
|
||||
|
||||
if (neighbourhood) {
|
||||
locationString = [NSString stringWithFormat:@"%@", neighbourhood];
|
||||
} else if (locality && county) {
|
||||
locationString = [NSString stringWithFormat:@"%@, %@", locality, county];
|
||||
} else if (region) {
|
||||
locationString = [NSString stringWithFormat:@"%@, %@", region, country];
|
||||
} else if (country) {
|
||||
locationString = [NSString stringWithFormat:@"%@", country];
|
||||
} else {
|
||||
locationString = @"ERROR";
|
||||
}
|
||||
|
||||
// NSLog(@"%@", photoLocationDictionary);
|
||||
// NSLog(@"%@, %@, %@, %@, %@", neighbourhood, locality, county, region, country);
|
||||
// NSLog(@"%@", locationString);
|
||||
|
||||
return locationString;
|
||||
}
|
||||
|
||||
- (void)beginReverseGeocodingLocationFromCoordinates
|
||||
{
|
||||
if (_placemarkFetchInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
_placemarkFetchInProgress = YES;
|
||||
|
||||
CLLocation *location = [[CLLocation alloc] initWithLatitude:_coordinates.latitude longitude:_coordinates.longitude];
|
||||
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
|
||||
|
||||
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
|
||||
|
||||
// completion handler gets called on main thread
|
||||
_placemark = [placemarks lastObject];
|
||||
_locationString = [self locationStringFromCLPlacemark];
|
||||
|
||||
// check if completion block set, call it - DO NOT CALL A NIL BLOCK!
|
||||
if (_placemarkCallbackBlock) {
|
||||
|
||||
// call the block with arguments
|
||||
_placemarkCallbackBlock(self);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (nullable NSString *)locationStringFromCLPlacemark
|
||||
{
|
||||
// early return if no location info
|
||||
if (!_placemark)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *name; // eg. Apple Inc.
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *thoroughfare; // street name, eg. Infinite Loop
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *subThoroughfare; // eg. 1
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *locality; // city, eg. Cupertino
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *subLocality; // neighborhood, common name, eg. Mission District
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *administrativeArea; // state, eg. CA
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *subAdministrativeArea; // county, eg. Santa Clara
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *postalCode; // zip code, eg. 95014
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *ISOcountryCode; // eg. US
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *country; // eg. United States
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *inlandWater; // eg. Lake Tahoe
|
||||
// @property (nonatomic, readonly, copy, nullable) NSString *ocean; // eg. Pacific Ocean
|
||||
// @property (nonatomic, readonly, copy, nullable) NSArray<NSString *> *areasOfInterest; // eg. Golden Gate Park
|
||||
|
||||
NSString *locationString;
|
||||
|
||||
if (_placemark.inlandWater) {
|
||||
locationString = _placemark.inlandWater;
|
||||
}
|
||||
// else if (_placemark.name) {
|
||||
// locationString = [NSString stringWithFormat:@"%@", _placemark.name];
|
||||
// }
|
||||
else if (_placemark.subLocality && _placemark.locality) {
|
||||
locationString = [NSString stringWithFormat:@"%@, %@", _placemark.subLocality, _placemark.locality];
|
||||
} else if (_placemark.administrativeArea && _placemark.subAdministrativeArea) {
|
||||
locationString = [NSString stringWithFormat:@"%@, %@", _placemark.subAdministrativeArea, _placemark.administrativeArea];
|
||||
} else if (_placemark.country) {
|
||||
locationString = _placemark.country;
|
||||
} else {
|
||||
locationString = @"ERROR";
|
||||
}
|
||||
|
||||
return locationString;
|
||||
}
|
||||
|
||||
@end
|
||||
13
examples/ASDKgram/Sample/MoreInfoViewController.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// MoreInfoViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/21/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface MoreInfoViewController : ASViewController
|
||||
|
||||
@end
|
||||
35
examples/ASDKgram/Sample/MoreInfoViewController.m
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// MoreInfoViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/21/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MoreInfoViewController.h"
|
||||
|
||||
@implementation MoreInfoViewController
|
||||
{
|
||||
ASTextNode *_textNode;
|
||||
}
|
||||
|
||||
//- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER;
|
||||
//
|
||||
//{
|
||||
// self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
//
|
||||
// if (self) {
|
||||
//
|
||||
// self.navigationItem.title = @"500pixgram";
|
||||
// [self.navigationController setNavigationBarHidden:YES];
|
||||
//
|
||||
// _textNode = [[ASTextNode alloc] init];
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return self;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
23
examples/ASDKgram/Sample/PhotoCellNode.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// PhotoCellNode.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CLLocation.h>
|
||||
#import "PhotoModel.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "PhotoTableViewCell.h" // PhotoTableViewCellProtocol
|
||||
|
||||
|
||||
@interface PhotoCellNode : ASCellNode
|
||||
|
||||
@property (nonatomic, strong, readwrite) id<PhotoTableViewCellProtocol> delegate;
|
||||
|
||||
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
|
||||
- (void)loadCommentsForPhoto:(PhotoModel *)photo;
|
||||
|
||||
@end
|
||||
218
examples/ASDKgram/Sample/PhotoCellNode.m
Normal file
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// PhotoCellNode.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoCellNode.h"
|
||||
#import "Utilities.h"
|
||||
#import "AsyncDisplayKit.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "CommentsNode.h"
|
||||
#import "PINImageView+PINRemoteImage.h"
|
||||
#import "PINButton+PINRemoteImage.h"
|
||||
|
||||
#define DEBUG_PHOTOCELL_LAYOUT 0
|
||||
|
||||
#define HEADER_HEIGHT 50
|
||||
#define USER_IMAGE_HEIGHT 50
|
||||
#define HORIZONTAL_BUFFER 10
|
||||
#define VERTICAL_BUFFER 5
|
||||
#define FONT_SIZE 14
|
||||
|
||||
@interface PhotoCellNode () <UIActionSheetDelegate>
|
||||
@end
|
||||
|
||||
@implementation PhotoCellNode
|
||||
{
|
||||
PhotoModel *_photoModel;
|
||||
CommentsNode *_photoCommentsView;
|
||||
ASNetworkImageNode *_userAvatarImageView;
|
||||
ASNetworkImageNode *_photoImageView;
|
||||
ASTextNode *_userNameLabel;
|
||||
ASTextNode *_photoLocationLabel;
|
||||
ASTextNode *_photoTimeIntervalSincePostLabel;
|
||||
ASTextNode *_photoLikesLabel;
|
||||
ASTextNode *_photoDescriptionLabel;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_photoModel = photo;
|
||||
|
||||
_userAvatarImageView = [[ASNetworkImageNode alloc] init];
|
||||
_userAvatarImageView.URL = photo.ownerUserProfile.userPicURL; // FIXME: make round
|
||||
|
||||
// FIXME: autocomplete for this line seems broken
|
||||
[_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) {
|
||||
CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
|
||||
return [image makeCircularImageWithSize:profileImageSize];
|
||||
}];
|
||||
|
||||
_photoImageView = [[ASNetworkImageNode alloc] init];
|
||||
_photoImageView.URL = photo.URL;
|
||||
_photoImageView.layerBacked = YES;
|
||||
|
||||
_userNameLabel = [[ASTextNode alloc] init];
|
||||
_userNameLabel.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
|
||||
_photoLocationLabel = [[ASTextNode alloc] init];
|
||||
_photoLocationLabel.maximumNumberOfLines = 1;
|
||||
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
|
||||
|
||||
// check and make sure this is still relevant for this cell (and not an old cell)
|
||||
// make sure to use _photoModel instance variable as photo may change when cell is reused,
|
||||
// where as local variable will never change
|
||||
if (locationModel == _photoModel.location) {
|
||||
_photoLocationLabel.attributedString = [[NSAttributedString alloc] initWithString:@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"];
|
||||
// _photoLocationLabel.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
|
||||
_photoTimeIntervalSincePostLabel = [[ASTextNode alloc] init];
|
||||
_photoTimeIntervalSincePostLabel.layerBacked = YES;
|
||||
_photoTimeIntervalSincePostLabel.attributedString = [photo uploadDateAttributedStringWithFontSize:FONT_SIZE];
|
||||
|
||||
_photoLikesLabel = [[ASTextNode alloc] init];
|
||||
_photoLikesLabel.layerBacked = YES;
|
||||
_photoLikesLabel.attributedString = [photo likesAttributedStringWithFontSize:FONT_SIZE];
|
||||
|
||||
_photoDescriptionLabel = [[ASTextNode alloc] init];
|
||||
_photoDescriptionLabel.layerBacked = YES;
|
||||
_photoDescriptionLabel.attributedString = [photo descriptionAttributedStringWithFontSize:FONT_SIZE];
|
||||
_photoDescriptionLabel.maximumNumberOfLines = 3;
|
||||
|
||||
_photoCommentsView = [[CommentsNode alloc] init];
|
||||
_photoCommentsView.shouldRasterizeDescendants = YES;
|
||||
|
||||
// instead of adding everything addSubnode:
|
||||
self.usesImplicitHierarchyManagement = YES;
|
||||
|
||||
// UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasLongPressed:)];
|
||||
// [self.view addGestureRecognizer:lpgr];
|
||||
//
|
||||
// // tap gesture recognizer
|
||||
// UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasTapped:)];
|
||||
// [self.view addGestureRecognizer:tgr];
|
||||
|
||||
[_userAvatarImageView addTarget:self action:@selector(doNothing) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[_photoLikesLabel addTarget:self action:@selector(doNothing) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[_userNameLabel addTarget:self action:@selector(doNothing) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
_userAvatarImageView.hitTestSlop = UIEdgeInsetsMake(-10, -10, -10, -20);
|
||||
_photoLikesLabel.hitTestSlop = UIEdgeInsetsMake(5, 5, 5, 5);
|
||||
|
||||
|
||||
#if DEBUG_PHOTOCELL_LAYOUT
|
||||
_userAvatarImageView.backgroundColor = [UIColor greenColor];
|
||||
_userNameLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoLocationLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoTimeIntervalSincePostLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoCommentsView.backgroundColor = [UIColor greenColor];
|
||||
_photoDescriptionLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoLikesLabel.backgroundColor = [UIColor greenColor];
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)doNothing
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
// username / photo location header vertical stack
|
||||
|
||||
CGFloat cellWidth = constrainedSize.max.width;
|
||||
// CGFloat locationWidth = HORIZONTAL_BUFFER * 3;
|
||||
//cellWidth - HORIZONTAL_BUFFER - USER_IMAGE_HEIGHT - HORIZONTAL_BUFFER - HORIZONTAL_BUFFER - _photoTimeIntervalSincePostLabel.frame.size.width - HORIZONTAL_BUFFER;
|
||||
// CGSize maxSize = CGSizeMake(locationWidth, CGFLOAT_MAX);
|
||||
// CGSize minSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
// _photoLocationLabel.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(minSize), ASRelativeSizeMakeWithCGSize(maxSize));
|
||||
_photoLocationLabel.flexShrink = YES;
|
||||
_userNameLabel.flexShrink = YES;
|
||||
|
||||
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
headerSubStack.flexShrink = YES;
|
||||
|
||||
if (_photoLocationLabel.attributedString) {
|
||||
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
|
||||
} else {
|
||||
[headerSubStack setChildren:@[_userNameLabel]];
|
||||
}
|
||||
|
||||
// header stack
|
||||
|
||||
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
|
||||
_photoTimeIntervalSincePostLabel.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer
|
||||
|
||||
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; // FIXME: long locations overflow post time - set max size?
|
||||
spacer.flexGrow = YES;
|
||||
spacer.flexShrink = YES;
|
||||
|
||||
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
|
||||
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack
|
||||
|
||||
UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER);
|
||||
ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView];
|
||||
|
||||
headerStack.flexShrink = YES;
|
||||
|
||||
[headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]];
|
||||
|
||||
// header inset stack
|
||||
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER);
|
||||
ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack];
|
||||
headerWithInset.flexShrink = YES;
|
||||
|
||||
|
||||
// footer stack
|
||||
|
||||
ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
footerStack.spacing = VERTICAL_BUFFER;
|
||||
|
||||
[footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel, _photoCommentsView]];
|
||||
|
||||
// footer inset stack
|
||||
|
||||
UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER);
|
||||
ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:footerStack];
|
||||
|
||||
// vertical stack
|
||||
|
||||
|
||||
_photoImageView.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size
|
||||
|
||||
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
|
||||
[verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]];
|
||||
verticalStack.flexShrink = YES;
|
||||
|
||||
return verticalStack;
|
||||
}
|
||||
|
||||
- (void)loadCommentsForPhoto:(PhotoModel *)photo
|
||||
{
|
||||
if (photo.commentFeed.numberOfItemsInFeed > 0) {
|
||||
[_photoCommentsView updateWithCommentFeedModel:photo.commentFeed];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
16
examples/ASDKgram/Sample/PhotoCollectionViewCell.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PhotoCollectionViewCell.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/2/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "PhotoModel.h"
|
||||
|
||||
@interface PhotoCollectionViewCell : UICollectionViewCell
|
||||
|
||||
- (void)updateCellWithPhotoObject:(PhotoModel *)photo;
|
||||
|
||||
@end
|
||||
71
examples/ASDKgram/Sample/PhotoCollectionViewCell.m
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// PhotoCollectionViewCell.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/2/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoCollectionViewCell.h"
|
||||
#import "PINImageView+PINRemoteImage.h"
|
||||
#import "PINButton+PINRemoteImage.h"
|
||||
|
||||
@implementation PhotoCollectionViewCell
|
||||
{
|
||||
UIImageView *_photoImageView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
|
||||
_photoImageView = [[UIImageView alloc] init];
|
||||
[_photoImageView setPin_updateWithProgress:YES];
|
||||
[self.contentView addSubview:_photoImageView];
|
||||
|
||||
// tap gesture recognizer
|
||||
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasTapped:)];
|
||||
[self addGestureRecognizer:tgr];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
_photoImageView.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
// remove images so that the old content doesn't appear before the new content is loaded
|
||||
_photoImageView.image = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (void)updateCellWithPhotoObject:(PhotoModel *)photo
|
||||
{
|
||||
// async download of photo using PINRemoteImage
|
||||
[_photoImageView pin_setImageFromURL:photo.URL];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Gesture Handling
|
||||
|
||||
- (void)cellWasTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
NSLog(@"Photo was tapped");
|
||||
}
|
||||
|
||||
@end
|
||||
35
examples/ASDKgram/Sample/PhotoFeedModel.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// PhotoFeedModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/28/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PhotoModel.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, PhotoFeedModelType) {
|
||||
PhotoFeedModelTypePopular,
|
||||
PhotoFeedModelTypeLocation,
|
||||
PhotoFeedModelTypeUserPhotos
|
||||
};
|
||||
|
||||
@interface PhotoFeedModel : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (NSUInteger)totalNumberOfPhotos;
|
||||
- (NSUInteger)numberOfItemsInFeed;
|
||||
- (PhotoModel *)objectAtIndex:(NSUInteger)index;
|
||||
- (NSInteger)indexOfPhotoModel:(PhotoModel *)photoModel;
|
||||
|
||||
- (void)updatePhotoFeedModelTypeLocationCoordinates:(CLLocationCoordinate2D)coordinate radiusInMiles:(NSUInteger)radius;
|
||||
- (void)updatePhotoFeedModelTypeUserId:(NSUInteger)userID;
|
||||
|
||||
- (void)clearFeed;
|
||||
- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults;
|
||||
- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults;
|
||||
|
||||
@end
|
||||
271
examples/ASDKgram/Sample/PhotoFeedModel.m
Normal file
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// PhotoFeedModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/28/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoFeedModel.h"
|
||||
#import "ImageURLModel.h"
|
||||
|
||||
#define fiveHundredPX_ENDPOINT_HOST @"https://api.500px.com/v1/"
|
||||
#define fiveHundredPX_ENDPOINT_POPULAR @"photos?feature=popular&sort=rating&image_size=3&include_store=store_download&include_states=voted"
|
||||
#define fiveHundredPX_ENDPOINT_SEARCH @"photos/search?geo=" //latitude,longitude,radius<units>
|
||||
#define fiveHundredPX_ENDPOINT_USER @"photos?user_id="
|
||||
#define fiveHundredPX_CONSUMER_KEY_PARAM @"&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC"
|
||||
|
||||
|
||||
@implementation PhotoFeedModel
|
||||
{
|
||||
PhotoFeedModelType _feedType;
|
||||
|
||||
NSMutableArray *_photos; // array of PhotoModel objects
|
||||
NSMutableArray *_ids;
|
||||
|
||||
CGSize _imageSize;
|
||||
NSString *_urlString;
|
||||
NSUInteger _currentPage;
|
||||
NSUInteger _totalPages;
|
||||
NSUInteger _totalItems;
|
||||
BOOL _fetchPageInProgress;
|
||||
BOOL _refreshFeedInProgress;
|
||||
|
||||
CLLocationCoordinate2D _location;
|
||||
NSUInteger _locationRadius;
|
||||
NSUInteger _userID;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSMutableArray *)photos
|
||||
{
|
||||
return _photos;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithPhotoFeedModelType:(PhotoFeedModelType)type imageSize:(CGSize)size
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_feedType = type;
|
||||
_imageSize = size;
|
||||
_photos = [[NSMutableArray alloc] init];
|
||||
_ids = [[NSMutableArray alloc] init];
|
||||
_currentPage = 0;
|
||||
|
||||
|
||||
NSString *apiEndpointString;
|
||||
|
||||
switch (type) {
|
||||
case (PhotoFeedModelTypePopular):
|
||||
apiEndpointString = fiveHundredPX_ENDPOINT_POPULAR;
|
||||
break;
|
||||
|
||||
case (PhotoFeedModelTypeLocation):
|
||||
apiEndpointString = fiveHundredPX_ENDPOINT_SEARCH;
|
||||
break;
|
||||
|
||||
case (PhotoFeedModelTypeUserPhotos):
|
||||
apiEndpointString = fiveHundredPX_ENDPOINT_USER;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_urlString = [[fiveHundredPX_ENDPOINT_HOST stringByAppendingString:apiEndpointString] stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (NSUInteger)totalNumberOfPhotos
|
||||
{
|
||||
return _totalItems;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfItemsInFeed
|
||||
{
|
||||
return [_photos count];
|
||||
}
|
||||
|
||||
- (PhotoModel *)objectAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [_photos objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSInteger)indexOfPhotoModel:(PhotoModel *)photoModel
|
||||
{
|
||||
return [_photos indexOfObjectIdenticalTo:photoModel];
|
||||
}
|
||||
|
||||
- (void)updatePhotoFeedModelTypeLocationCoordinates:(CLLocationCoordinate2D)coordinate radiusInMiles:(NSUInteger)radius;
|
||||
{
|
||||
_location = coordinate;
|
||||
_locationRadius = radius;
|
||||
NSString *locationString = [NSString stringWithFormat:@"%f,%f,%lumi", coordinate.latitude, coordinate.longitude, (unsigned long)radius];
|
||||
|
||||
_urlString = [fiveHundredPX_ENDPOINT_HOST stringByAppendingString:fiveHundredPX_ENDPOINT_SEARCH];
|
||||
_urlString = [[_urlString stringByAppendingString:locationString] stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM];
|
||||
}
|
||||
|
||||
- (void)updatePhotoFeedModelTypeUserId:(NSUInteger)userID
|
||||
{
|
||||
_userID = userID;
|
||||
|
||||
NSString *userString = [NSString stringWithFormat:@"%lu", (long)userID];
|
||||
_urlString = [fiveHundredPX_ENDPOINT_HOST stringByAppendingString:fiveHundredPX_ENDPOINT_USER];
|
||||
_urlString = [[_urlString stringByAppendingString:userString] stringByAppendingString:@"&sort=created_at&image_size=3&include_store=store_download&include_states=voted"];
|
||||
_urlString = [_urlString stringByAppendingString:fiveHundredPX_CONSUMER_KEY_PARAM];
|
||||
}
|
||||
|
||||
- (void)clearFeed
|
||||
{
|
||||
_photos = [[NSMutableArray alloc] init];
|
||||
_ids = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
- (void)requestPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults
|
||||
{
|
||||
// only one fetch at a time
|
||||
if (_fetchPageInProgress) {
|
||||
|
||||
// NSLog(@"Request: FAIL - fetch page already in progress");
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
_fetchPageInProgress = YES;
|
||||
|
||||
// NSLog(@"Request: SUCCESS");
|
||||
[self fetchPageWithCompletionBlock:block numResultsToReturn:numResults];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshFeedWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults
|
||||
{
|
||||
// only one fetch at a time
|
||||
if (_refreshFeedInProgress) {
|
||||
|
||||
// NSLog(@"Request Refresh: FAIL - refresh feed already in progress");
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
_refreshFeedInProgress = YES;
|
||||
_currentPage = 0;
|
||||
|
||||
// FIXME: blow away any other requests in progress
|
||||
|
||||
// NSLog(@"Request Refresh: SUCCESS");
|
||||
|
||||
|
||||
[self fetchPageWithCompletionBlock:^(NSArray *newPhotos) {
|
||||
if (block) {
|
||||
block(newPhotos);
|
||||
}
|
||||
|
||||
_refreshFeedInProgress = NO;
|
||||
} numResultsToReturn:numResults replaceData:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults
|
||||
{
|
||||
[self fetchPageWithCompletionBlock:block numResultsToReturn:numResults replaceData:NO];
|
||||
}
|
||||
|
||||
- (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToReturn:(NSUInteger)numResults replaceData:(BOOL)replaceData
|
||||
{
|
||||
// early return if reached end of pages
|
||||
if (_totalPages) {
|
||||
if (_currentPage == _totalPages) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// rpp cannot be over 100
|
||||
NSUInteger numPhotos = (numResults < 100) ? numResults : 100;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
NSMutableArray *newPhotos = [NSMutableArray array];
|
||||
NSMutableArray *newIDs = [NSMutableArray array];
|
||||
|
||||
@synchronized(self) {
|
||||
|
||||
NSUInteger nextPage = _currentPage + 1;
|
||||
NSString *imageSizeParam = [ImageURLModel imageParameterForClosestImageSize:_imageSize];
|
||||
NSString *urlAdditions = [NSString stringWithFormat:@"&page=%lu&rpp=%lu%@", (unsigned long)nextPage, (long)numPhotos, imageSizeParam];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:[_urlString stringByAppendingString:urlAdditions]];
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfURL:url];
|
||||
|
||||
if (data) {
|
||||
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
|
||||
|
||||
if ([response isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
_currentPage = [[response valueForKeyPath:@"current_page"] integerValue];
|
||||
_totalPages = [[response valueForKeyPath:@"total_pages"] integerValue];
|
||||
_totalItems = [[response valueForKeyPath:@"total_items"] integerValue];
|
||||
|
||||
NSArray *photos = [response valueForKeyPath:@"photos"];
|
||||
|
||||
if ([photos isKindOfClass:[NSArray class]]) {
|
||||
|
||||
for (NSDictionary *photoDictionary in photos) {
|
||||
|
||||
if ([response isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
PhotoModel *photo = [[PhotoModel alloc] initWith500pxPhoto:photoDictionary];
|
||||
|
||||
// addObject: will crash with nil (NSArray, NSSet, NSDictionary, URLWithString - most foundation things)
|
||||
if (photo) {
|
||||
|
||||
if (replaceData || ![_ids containsObject:photo.photoID]) {
|
||||
[newPhotos addObject:photo];
|
||||
[newIDs addObject:photo.photoID];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (replaceData) {
|
||||
_photos = [newPhotos mutableCopy];
|
||||
_ids = [newIDs mutableCopy];
|
||||
} else {
|
||||
[_photos addObjectsFromArray:newPhotos];
|
||||
[_ids addObjectsFromArray:newIDs];
|
||||
}
|
||||
|
||||
if (block) {
|
||||
block(newPhotos);
|
||||
}
|
||||
});
|
||||
|
||||
_fetchPageInProgress = NO;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
14
examples/ASDKgram/Sample/PhotoFeedNodeController.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// PhotoFeedNodeController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface PhotoFeedNodeController : UIViewController
|
||||
|
||||
|
||||
@end
|
||||
273
examples/ASDKgram/Sample/PhotoFeedNodeController.m
Normal file
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// PhotoFeedNodeController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoFeedNodeController.h"
|
||||
#import "PhotoModel.h"
|
||||
#import "PhotoCellNode.h"
|
||||
#import "PhotoTableViewCell.h"
|
||||
#import "UserProfileViewController.h"
|
||||
#import "LocationCollectionViewController.h"
|
||||
#import "PhotoFeedModel.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "Utilities.h"
|
||||
|
||||
#define AUTO_TAIL_LOADING_NUM_SCREENFULS 2.5
|
||||
|
||||
@interface PhotoFeedNodeController () <ASTableDelegate, ASTableDataSource, PhotoTableViewCellProtocol>
|
||||
@end
|
||||
|
||||
@implementation PhotoFeedNodeController
|
||||
{
|
||||
PhotoFeedModel *_photoFeed;
|
||||
ASTableView *_tableView;
|
||||
UIView *_statusBarOpaqueUnderlayView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
|
||||
{
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.navigationItem.title = @"500pixgram";
|
||||
[self.navigationController setNavigationBarHidden:YES];
|
||||
|
||||
// _tableView.refreshControl = [[UIRefreshControl alloc] init];
|
||||
// [self.refreshControl addTarget:self action:@selector(refreshFeed) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
_photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]];
|
||||
[self refreshFeed];
|
||||
|
||||
_statusBarOpaqueUnderlayView = [[UIView alloc] init];
|
||||
_statusBarOpaqueUnderlayView.backgroundColor = [UIColor darkBlueColor];
|
||||
[[[UIApplication sharedApplication] keyWindow] addSubview:_statusBarOpaqueUnderlayView];
|
||||
|
||||
|
||||
// ASTABLEVIEW
|
||||
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES];
|
||||
_tableView.asyncDataSource = self;
|
||||
_tableView.asyncDelegate = self;
|
||||
_tableView.allowsSelection = NO;
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
|
||||
// enable tableView pull-to-refresh & add target-action pair
|
||||
// self.refreshControl = [[UIRefreshControl alloc] init];
|
||||
// [self.refreshControl addTarget:self action:@selector(refreshFeed) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)imageSizeForScreenWidth
|
||||
{
|
||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
||||
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
||||
return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale);
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
[self.view addSubview:_tableView]; //FIXME: self use implicit heirarchy?
|
||||
self.view.backgroundColor = [UIColor whiteColor]; //ditto
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.navigationController.hidesBarsOnSwipe = YES;
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
_tableView.frame = self.view.bounds;
|
||||
_statusBarOpaqueUnderlayView.frame = [[UIApplication sharedApplication] statusBarFrame];
|
||||
}
|
||||
|
||||
#pragma mark - Gesture Handling
|
||||
|
||||
- (void)refreshFeed
|
||||
{
|
||||
// small first batch
|
||||
[_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){
|
||||
|
||||
[_tableView reloadData]; // overwrite tableView instead of inserting new rows
|
||||
// [self.refreshControl endRefreshing];
|
||||
[self requestCommentsForPhotos:newPhotos];
|
||||
|
||||
// immediately start second larger fetch
|
||||
[self loadPage];
|
||||
|
||||
} numResultsToReturn:4];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle // FIXME - doesn't work?
|
||||
{
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (void)loadPage
|
||||
{
|
||||
// NSLog(@"_photoFeed number of items = %lu", [_photoFeed numberOfItemsInFeed]);
|
||||
|
||||
[self logPhotoIDsInPhotoFeed];
|
||||
|
||||
[_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
||||
|
||||
[self insertNewRowsInTableView:newPhotos];
|
||||
[self requestCommentsForPhotos:newPhotos];
|
||||
[self logPhotoIDsInPhotoFeed];
|
||||
|
||||
} numResultsToReturn:20];
|
||||
}
|
||||
|
||||
- (void)requestCommentsForPhotos:(NSArray *)newPhotos
|
||||
{
|
||||
for (PhotoModel *photo in newPhotos) {
|
||||
|
||||
[photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) {
|
||||
|
||||
NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo];
|
||||
PhotoCellNode *cell = (PhotoCellNode *)[_tableView nodeForRowAtIndexPath:[NSIndexPath indexPathForRow:rowNum inSection:0]];
|
||||
|
||||
if (cell) {
|
||||
[cell loadCommentsForPhoto:photo];
|
||||
[_tableView beginUpdates];
|
||||
[_tableView endUpdates];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logPhotoIDsInPhotoFeed
|
||||
{
|
||||
// NSLog(@"_photoFeed number of items = %lu", [_photoFeed numberOfItemsInFeed]);
|
||||
|
||||
// for (int i = 0; i < [_photoFeed numberOfItemsInFeed]; i++) {
|
||||
// if (i % 4 == 0 && i > 0) {
|
||||
// NSLog(@"\t-----");
|
||||
// }
|
||||
|
||||
// [_photoFeed return]
|
||||
// NSString *duplicate = ? @"(DUPLICATE)" : @"";
|
||||
// NSLog(@"\t%@ %@", [[_photoFeed objectAtIndex:i] photoID], @"");
|
||||
// }
|
||||
}
|
||||
|
||||
- (void)insertNewRowsInTableView:(NSArray *)newPhotos
|
||||
{
|
||||
// NSLog(@"_photoFeed number of items = %lu (%lu total)", (unsigned long)[_photoFeed numberOfItemsInFeed], (long)[_photoFeed totalNumberOfPhotos]);
|
||||
|
||||
// instead of doing tableView reloadData, use table editing commands
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
|
||||
NSInteger section = 0;
|
||||
NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed];
|
||||
for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) {
|
||||
|
||||
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
[indexPaths addObject:path];
|
||||
}
|
||||
|
||||
[_tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}
|
||||
|
||||
#pragma mark - ASTableDelegate protocol methods
|
||||
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
|
||||
CGFloat currentOffSetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
|
||||
|
||||
// automatic tail loading
|
||||
CGFloat screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight;
|
||||
if (screenfullsBeforeBottom < AUTO_TAIL_LOADING_NUM_SCREENFULS) {
|
||||
// NSLog(@"AUTOMATIC TAIL LOADING BEGIN");
|
||||
[self loadPage];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - ASTableDataSource protocol methods
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [_photoFeed numberOfItemsInFeed];
|
||||
}
|
||||
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
{
|
||||
PhotoCellNode *cell = [[PhotoCellNode alloc] initWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - PhotoTableViewCellProtocol
|
||||
|
||||
- (void)photoLikesWasTouchedWithPhoto:(PhotoModel *)photo
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)userProfileWasTouchedWithUser:(UserModel *)user
|
||||
{
|
||||
UserProfileViewController *userProfileView = [[UserProfileViewController alloc] initWithUser:user];
|
||||
|
||||
[self.navigationController pushViewController:userProfileView animated:YES];
|
||||
}
|
||||
|
||||
- (void)photoLocationWasTouchedWithCoordinate:(CLLocationCoordinate2D)coordiantes name:(NSString *)name
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.minimumInteritemSpacing = 1;
|
||||
layout.minimumLineSpacing = 1;
|
||||
layout.headerReferenceSize = CGSizeMake(self.view.bounds.size.width, 200);
|
||||
|
||||
CGFloat numItemsLine = 3;
|
||||
layout.itemSize = CGSizeMake((self.view.bounds.size.width - (numItemsLine - 1)) / numItemsLine,
|
||||
(self.view.bounds.size.width - (numItemsLine - 1)) / numItemsLine);
|
||||
|
||||
LocationCollectionViewController *locationCVC = [[LocationCollectionViewController alloc] initWithCollectionViewLayout:layout coordinates:coordiantes];
|
||||
locationCVC.navigationItem.title = name;
|
||||
|
||||
[self.navigationController pushViewController:locationCVC animated:YES];
|
||||
}
|
||||
|
||||
- (void)cellWasLongPressedWithPhoto:(PhotoModel *)photo
|
||||
{
|
||||
UIAlertAction *savePhotoAction = [UIAlertAction actionWithTitle:@"Save Photo"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
NSLog(@"hi");
|
||||
}];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
|
||||
}];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
[alert addAction:savePhotoAction];
|
||||
[alert addAction:cancelAction];
|
||||
|
||||
[self presentViewController:alert animated:YES completion:^{}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
13
examples/ASDKgram/Sample/PhotoMapViewController.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// PhotoMapViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/2/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface PhotoMapViewController : UIViewController
|
||||
|
||||
@end
|
||||
195
examples/ASDKgram/Sample/PhotoMapViewController.m
Normal file
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// PhotoMapViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/2/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoMapViewController.h"
|
||||
#import <MapKit/MKMapView.h>
|
||||
#import <MapKit/MKPointAnnotation.h>
|
||||
#import <MapKit/MKUserLocation.h>
|
||||
|
||||
@interface PhotoMapViewController () <MKMapViewDelegate, CLLocationManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation PhotoMapViewController
|
||||
{
|
||||
MKMapView *_mapView;
|
||||
UIButton *_mapCrosshairsBtn;
|
||||
CLLocationManager *_locationManager;
|
||||
BOOL _userLocationEnabled;
|
||||
BOOL _CLLocationAuthorizationStatusDeniedAlertPresentedOnce;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
|
||||
{
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.navigationItem.title = @"Discover Nearby Photos";
|
||||
|
||||
// location manager
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
_locationManager.delegate = self;
|
||||
|
||||
// map view
|
||||
_mapView = [[MKMapView alloc] init];
|
||||
_mapView.delegate = self;
|
||||
|
||||
[self.view addSubview:_mapView];
|
||||
|
||||
// map view - crosshairs button
|
||||
_mapCrosshairsBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_mapCrosshairsBtn setImage:[UIImage imageNamed:@"crosshairs"] forState:UIControlStateNormal];
|
||||
[_mapCrosshairsBtn addTarget:self action:@selector(centerMapOnUsersLocationIfLocationEnabled) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self.view addSubview:_mapCrosshairsBtn];
|
||||
|
||||
// check App's CLLocation authorization status
|
||||
[self checkCLAuthorizationStatusWithAlertForStatusDenied:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// check App's CLLocation authorization status & prompt user to enable if not authorized
|
||||
[self checkCLAuthorizationStatusWithAlertForStatusDenied:YES];
|
||||
|
||||
if (_userLocationEnabled) {
|
||||
|
||||
// if the user's location is enabled show it on the map
|
||||
_mapView.showsUserLocation = YES;
|
||||
|
||||
[_locationManager startUpdatingLocation];
|
||||
|
||||
// check if user's Location is in mapView visible area, if not center the map on the user
|
||||
MKMapPoint userPoint = MKMapPointForCoordinate(_mapView.userLocation.location.coordinate);
|
||||
MKMapRect mapRect = _mapView.visibleMapRect;
|
||||
BOOL inside = MKMapRectContainsPoint(mapRect, userPoint);
|
||||
if (!inside) {
|
||||
[self centerMapOnUsersLocation];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MAP_CROSSHAIRS_BTN_WIDTH 30
|
||||
#define MAP_CROSSHAIRS_BTN_INSET 20
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
// FIXME: tune into NSNotification
|
||||
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.view.bounds.size;
|
||||
|
||||
// layout map crosshairs button
|
||||
CGSize btnSize = CGSizeMake(MAP_CROSSHAIRS_BTN_WIDTH, MAP_CROSSHAIRS_BTN_WIDTH);
|
||||
CGFloat x = boundsSize.width - btnSize.width - MAP_CROSSHAIRS_BTN_INSET;
|
||||
CGFloat y = CGRectGetMinY(self.tabBarController.tabBar.frame) - btnSize.width - MAP_CROSSHAIRS_BTN_INSET;
|
||||
_mapCrosshairsBtn.frame = (CGRect) {CGPointMake(x, y), btnSize};
|
||||
|
||||
// layout mapView
|
||||
_mapView.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
if (_userLocationEnabled) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Functions
|
||||
|
||||
- (void)centerMapOnUsersLocationIfLocationEnabled
|
||||
{
|
||||
if (_userLocationEnabled) {
|
||||
[self centerMapOnUsersLocation];
|
||||
} else {
|
||||
[self checkCLAuthorizationStatusWithAlertForStatusDenied:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#define ZOOM_SPAN_DELTA 0.03
|
||||
- (void)centerMapOnUsersLocation
|
||||
{
|
||||
// set mapView region
|
||||
CLLocationCoordinate2D location;
|
||||
CLLocation *usersLocation = [_locationManager location];
|
||||
location.latitude = usersLocation.coordinate.latitude;
|
||||
location.longitude = usersLocation.coordinate.longitude;
|
||||
MKCoordinateSpan span = MKCoordinateSpanMake(ZOOM_SPAN_DELTA, ZOOM_SPAN_DELTA);
|
||||
MKCoordinateRegion region = MKCoordinateRegionMake(location, span);
|
||||
|
||||
[_mapView setRegion:region animated:YES];
|
||||
}
|
||||
|
||||
- (void)checkCLAuthorizationStatusWithAlertForStatusDenied:(BOOL)showDeniedAlert
|
||||
{
|
||||
// check app's permission to use location services
|
||||
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
|
||||
|
||||
if (status == kCLAuthorizationStatusNotDetermined) {
|
||||
|
||||
NSLog(@"App's permission to use location NOT DETERMINED");
|
||||
_userLocationEnabled = NO;
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
|
||||
} else if (status == kCLAuthorizationStatusDenied) {
|
||||
|
||||
NSLog(@"App's permission to use location DENIED");
|
||||
|
||||
_userLocationEnabled = NO;
|
||||
|
||||
if (showDeniedAlert) {
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
|
||||
message:@"Location services disabled. See Settings > Flickergram > Location to change."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *acknowledgeAction = [UIAlertAction actionWithTitle:@"Ok"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * _Nonnull action) {}];
|
||||
|
||||
[alertController addAction:acknowledgeAction];
|
||||
|
||||
[self presentViewController:alertController animated:YES completion:^{}];
|
||||
}
|
||||
|
||||
} else if (status == kCLAuthorizationStatusRestricted) {
|
||||
|
||||
NSLog(@"App's permission to use location RESTRICTED");
|
||||
_userLocationEnabled = NO;
|
||||
|
||||
} else {
|
||||
|
||||
NSLog(@"App has permission to use location");
|
||||
_userLocationEnabled = YES;
|
||||
_mapView.showsUserLocation = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MKMapKitDelegate
|
||||
|
||||
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
|
||||
{
|
||||
// FIXME:
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
|
||||
{
|
||||
[self checkCLAuthorizationStatusWithAlertForStatusDenied:NO];
|
||||
[self centerMapOnUsersLocationIfLocationEnabled];
|
||||
}
|
||||
|
||||
@end
|
||||
36
examples/ASDKgram/Sample/PhotoModel.h
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// PhotoModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CoreGraphics/CoreGraphics.h"
|
||||
#import "UserModel.h"
|
||||
#import "LocationModel.h"
|
||||
#import "CommentFeedModel.h"
|
||||
|
||||
@interface PhotoModel : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSURL *URL;
|
||||
@property (nonatomic, strong, readonly) NSString *photoID;
|
||||
@property (nonatomic, strong, readonly) NSString *uploadDateString;
|
||||
@property (nonatomic, strong, readonly) NSString *title;
|
||||
@property (nonatomic, strong, readonly) NSString *descriptionText;
|
||||
@property (nonatomic, assign, readonly) NSUInteger commentsCount;
|
||||
@property (nonatomic, assign, readonly) NSUInteger likesCount;
|
||||
@property (nonatomic, strong, readonly) LocationModel *location;
|
||||
@property (nonatomic, strong, readonly) UserModel *ownerUserProfile;
|
||||
@property (nonatomic, strong, readonly) CommentFeedModel *commentFeed;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWith500pxPhoto:(NSDictionary *)photoDictionary NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size;
|
||||
- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size;
|
||||
- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size;
|
||||
- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size;
|
||||
|
||||
@end
|
||||
117
examples/ASDKgram/Sample/PhotoModel.m
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// PhotoModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoModel.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "Utilities.h"
|
||||
|
||||
|
||||
@implementation PhotoModel
|
||||
{
|
||||
NSDictionary *_dictionaryRepresentation;
|
||||
NSString *_uploadDateRaw;
|
||||
CommentFeedModel *_commentFeed;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (CommentFeedModel *)commentFeed
|
||||
{
|
||||
if (!_commentFeed) {
|
||||
_commentFeed = [[CommentFeedModel alloc] initWithPhotoID:_photoID];
|
||||
}
|
||||
|
||||
return _commentFeed;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWith500pxPhoto:(NSDictionary *)photoDictionary
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_dictionaryRepresentation = photoDictionary;
|
||||
|
||||
NSString *urlString = [photoDictionary objectForKey:@"image_url"];
|
||||
_URL = urlString ? [NSURL URLWithString:urlString] : nil;
|
||||
|
||||
_ownerUserProfile = [[UserModel alloc] initWith500pxPhoto:photoDictionary];
|
||||
|
||||
_uploadDateRaw = [photoDictionary objectForKey:@"created_at"];
|
||||
|
||||
_photoID = [[photoDictionary objectForKey:@"id"] description];
|
||||
|
||||
_title = [photoDictionary objectForKey:@"title"];
|
||||
_descriptionText = [photoDictionary valueForKeyPath:@"name"];
|
||||
|
||||
_commentsCount = [[photoDictionary objectForKey:@"comments_count"] integerValue];
|
||||
_likesCount = [[photoDictionary objectForKey:@"positive_votes_count"] integerValue];
|
||||
|
||||
// photo location
|
||||
_location = [[LocationModel alloc] initWith500pxPhoto:photoDictionary];
|
||||
|
||||
// calculate dateString off the main thread
|
||||
_uploadDateString = [NSString elapsedTimeStringSinceDate:_uploadDateRaw];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
NSString *string = [NSString stringWithFormat:@"%@ %@", self.ownerUserProfile.username, self.descriptionText];
|
||||
NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string
|
||||
fontSize:size
|
||||
color:[UIColor darkGrayColor]
|
||||
firstWordColor:[UIColor darkBlueColor]];
|
||||
return attrString;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
return [NSAttributedString attributedStringWithString:self.uploadDateString
|
||||
fontSize:size
|
||||
color:[UIColor lightGrayColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
|
||||
NSString *formattedLikesNumber = [formatter stringFromNumber:[[NSNumber alloc] initWithUnsignedInteger:self.likesCount]];
|
||||
|
||||
NSString *likesString = [NSString stringWithFormat:@"♥︎ %@ likes", formattedLikesNumber];
|
||||
|
||||
return [NSAttributedString attributedStringWithString:likesString
|
||||
fontSize:size
|
||||
color:[UIColor darkBlueColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
return [NSAttributedString attributedStringWithString:self.location.locationString
|
||||
fontSize:size
|
||||
color:[UIColor lightBlueColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ - %@", _photoID, _descriptionText];
|
||||
}
|
||||
|
||||
@end
|
||||
30
examples/ASDKgram/Sample/PhotoTableViewCell.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// PhotoTableViewCell.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CLLocation.h>
|
||||
#import "PhotoModel.h"
|
||||
|
||||
@protocol PhotoTableViewCellProtocol <NSObject>
|
||||
- (void)userProfileWasTouchedWithUser:(UserModel *)user;
|
||||
- (void)photoLocationWasTouchedWithCoordinate:(CLLocationCoordinate2D)coordiantes name:(NSAttributedString *)name;
|
||||
- (void)cellWasLongPressedWithPhoto:(PhotoModel *)photo;
|
||||
- (void)photoLikesWasTouchedWithPhoto:(PhotoModel *)photo;
|
||||
@end
|
||||
|
||||
|
||||
@interface PhotoTableViewCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, strong, readwrite) id<PhotoTableViewCellProtocol> delegate;
|
||||
|
||||
+ (CGFloat)heightForPhotoModel:(PhotoModel *)photo withWidth:(CGFloat)width;
|
||||
|
||||
- (void)updateCellWithPhotoObject:(PhotoModel *)photo;
|
||||
- (void)loadCommentsForPhoto:(PhotoModel *)photo;
|
||||
|
||||
@end
|
||||
349
examples/ASDKgram/Sample/PhotoTableViewCell.m
Normal file
@@ -0,0 +1,349 @@
|
||||
//
|
||||
// PhotoTableViewCell.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoTableViewCell.h"
|
||||
#import "Utilities.h"
|
||||
#import "PINImageView+PINRemoteImage.h"
|
||||
#import "PINButton+PINRemoteImage.h"
|
||||
#import "CommentView.h"
|
||||
|
||||
#define DEBUG_PHOTOCELL_LAYOUT 0
|
||||
|
||||
#define HEADER_HEIGHT 50
|
||||
#define USER_IMAGE_HEIGHT 30
|
||||
#define HORIZONTAL_BUFFER 10
|
||||
#define VERTICAL_BUFFER 5
|
||||
#define FONT_SIZE 14
|
||||
|
||||
@interface PhotoTableViewCell () <UIActionSheetDelegate>
|
||||
@end
|
||||
|
||||
@implementation PhotoTableViewCell
|
||||
{
|
||||
PhotoModel *_photoModel;
|
||||
CommentView *_photoCommentsView;
|
||||
UIImageView *_userAvatarImageView;
|
||||
UIImageView *_photoImageView;
|
||||
UILabel *_userNameLabel;
|
||||
UILabel *_photoLocationLabel;
|
||||
UILabel *_photoTimeIntervalSincePostLabel;
|
||||
UILabel *_photoLikesLabel;
|
||||
UILabel *_photoDescriptionLabel;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
+ (CGFloat)heightForPhotoModel:(PhotoModel *)photo withWidth:(CGFloat)width;
|
||||
{
|
||||
CGFloat photoHeight = width;
|
||||
|
||||
UIFont *font = [UIFont systemFontOfSize:FONT_SIZE];
|
||||
CGFloat likesHeight = roundf([font lineHeight]);
|
||||
|
||||
NSAttributedString *descriptionAttrString = [photo descriptionAttributedStringWithFontSize:FONT_SIZE];
|
||||
CGFloat availableWidth = (width - HORIZONTAL_BUFFER * 2);
|
||||
CGFloat descriptionHeight = [descriptionAttrString boundingRectWithSize:CGSizeMake(availableWidth, CGFLOAT_MAX)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
context:nil].size.height;
|
||||
|
||||
CGFloat commentViewHeight = [CommentView heightForCommentFeedModel:photo.commentFeed withWidth:availableWidth];
|
||||
|
||||
return HEADER_HEIGHT + photoHeight + likesHeight + descriptionHeight + commentViewHeight + (4 * VERTICAL_BUFFER);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
||||
{
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
|
||||
if (self) {
|
||||
|
||||
_photoCommentsView = [[CommentView alloc] init];
|
||||
|
||||
_userAvatarImageView = [[UIImageView alloc] init];
|
||||
_photoImageView = [[UIImageView alloc] init];
|
||||
[_photoImageView setPin_updateWithProgress:YES];
|
||||
|
||||
_userNameLabel = [[UILabel alloc] init];
|
||||
_photoLocationLabel = [[UILabel alloc] init];
|
||||
_photoTimeIntervalSincePostLabel = [[UILabel alloc] init];
|
||||
_photoLikesLabel = [[UILabel alloc] init];
|
||||
_photoDescriptionLabel = [[UILabel alloc] init];
|
||||
_photoDescriptionLabel.numberOfLines = 3;
|
||||
|
||||
[self addSubview:_photoCommentsView];
|
||||
[self addSubview:_userAvatarImageView];
|
||||
[self addSubview:_photoImageView];
|
||||
[self addSubview:_userNameLabel];
|
||||
[self addSubview:_photoLocationLabel];
|
||||
[self addSubview:_photoTimeIntervalSincePostLabel];
|
||||
[self addSubview:_photoLikesLabel];
|
||||
[self addSubview:_photoDescriptionLabel];
|
||||
|
||||
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasLongPressed:)];
|
||||
[self addGestureRecognizer:lpgr];
|
||||
|
||||
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasTapped:)];
|
||||
[self addGestureRecognizer:tgr];
|
||||
|
||||
#if DEBUG_PHOTOCELL_LAYOUT
|
||||
_userAvatarImageView.backgroundColor = [UIColor greenColor];
|
||||
_userNameLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoLocationLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoTimeIntervalSincePostLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoCommentsView.backgroundColor = [UIColor greenColor];
|
||||
_photoDescriptionLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoLikesLabel.backgroundColor = [UIColor greenColor];
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.bounds.size;
|
||||
|
||||
// FIXME: Make PhotoCellHeaderView
|
||||
|
||||
CGRect rect = CGRectMake(HORIZONTAL_BUFFER, (HEADER_HEIGHT - USER_IMAGE_HEIGHT) / 2.0,
|
||||
USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
|
||||
_userAvatarImageView.frame = rect;
|
||||
|
||||
rect.size = _photoTimeIntervalSincePostLabel.bounds.size;
|
||||
rect.origin.x = boundsSize.width - HORIZONTAL_BUFFER - rect.size.width;
|
||||
rect.origin.y = (HEADER_HEIGHT - rect.size.height) / 2.0;
|
||||
_photoTimeIntervalSincePostLabel.frame = rect;
|
||||
|
||||
CGFloat availableWidth = CGRectGetMinX(_photoTimeIntervalSincePostLabel.frame) - HORIZONTAL_BUFFER;
|
||||
rect.size = _userNameLabel.bounds.size;
|
||||
rect.size.width = MIN(availableWidth, rect.size.width);
|
||||
|
||||
rect.origin.x = HORIZONTAL_BUFFER + USER_IMAGE_HEIGHT + HORIZONTAL_BUFFER;
|
||||
|
||||
if (_photoLocationLabel.attributedText) {
|
||||
CGSize locationSize = _photoLocationLabel.bounds.size;
|
||||
locationSize.width = MIN(availableWidth, locationSize.width);
|
||||
|
||||
rect.origin.y = (HEADER_HEIGHT - rect.size.height - locationSize.height) / 2.0;
|
||||
_userNameLabel.frame = rect;
|
||||
|
||||
// FIXME: Name rects at least for this sub-condition
|
||||
rect.origin.y += rect.size.height;
|
||||
rect.size = locationSize;
|
||||
_photoLocationLabel.frame = rect;
|
||||
} else {
|
||||
rect.origin.y = (HEADER_HEIGHT - rect.size.height) / 2.0;
|
||||
_userNameLabel.frame = rect;
|
||||
}
|
||||
|
||||
_photoImageView.frame = CGRectMake(0, HEADER_HEIGHT, boundsSize.width, boundsSize.width);
|
||||
|
||||
// FIXME: Make PhotoCellFooterView
|
||||
|
||||
rect.size = _photoLikesLabel.bounds.size;
|
||||
rect.origin = CGPointMake(HORIZONTAL_BUFFER, CGRectGetMaxY(_photoImageView.frame) + VERTICAL_BUFFER);
|
||||
_photoLikesLabel.frame = rect;
|
||||
|
||||
rect.size = _photoDescriptionLabel.bounds.size;
|
||||
rect.size.width = MIN(boundsSize.width - HORIZONTAL_BUFFER * 2, rect.size.width);
|
||||
rect.origin.y = CGRectGetMaxY(_photoLikesLabel.frame) + VERTICAL_BUFFER;
|
||||
_photoDescriptionLabel.frame = rect;
|
||||
|
||||
rect.size = _photoCommentsView.bounds.size;
|
||||
rect.size.width = boundsSize.width - HORIZONTAL_BUFFER * 2;
|
||||
rect.origin.y = CGRectGetMaxY(_photoDescriptionLabel.frame) + VERTICAL_BUFFER;
|
||||
_photoCommentsView.frame = rect;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
_photoCommentsView.frame = CGRectZero; // next cell might not have a _photoCommentsView
|
||||
[_photoCommentsView updateWithCommentFeedModel:nil];
|
||||
|
||||
_userAvatarImageView.image = nil;
|
||||
_photoImageView.image = nil;
|
||||
_userNameLabel.attributedText = nil;
|
||||
_photoLocationLabel.attributedText = nil;
|
||||
_photoLocationLabel.frame = CGRectZero; // next cell might not have a _photoLocationLabel
|
||||
_photoTimeIntervalSincePostLabel.attributedText = nil;
|
||||
_photoLikesLabel.attributedText = nil;
|
||||
_photoDescriptionLabel.attributedText = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (void)updateCellWithPhotoObject:(PhotoModel *)photo
|
||||
{
|
||||
_photoModel = photo;
|
||||
_userNameLabel.attributedText = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
_photoTimeIntervalSincePostLabel.attributedText = [photo uploadDateAttributedStringWithFontSize:FONT_SIZE];
|
||||
_photoLikesLabel.attributedText = [photo likesAttributedStringWithFontSize:FONT_SIZE];
|
||||
_photoDescriptionLabel.attributedText = [photo descriptionAttributedStringWithFontSize:FONT_SIZE];
|
||||
|
||||
[_userNameLabel sizeToFit];
|
||||
[_photoTimeIntervalSincePostLabel sizeToFit];
|
||||
[_photoLikesLabel sizeToFit];
|
||||
[_photoDescriptionLabel sizeToFit];
|
||||
CGRect rect = _photoDescriptionLabel.frame;
|
||||
CGFloat availableWidth = (self.bounds.size.width - HORIZONTAL_BUFFER * 2);
|
||||
rect.size = [_photoDescriptionLabel sizeThatFits:CGSizeMake(availableWidth, CGFLOAT_MAX)];
|
||||
_photoDescriptionLabel.frame = rect;
|
||||
|
||||
[_photoImageView pin_setImageFromURL:photo.URL];
|
||||
[self downloadAndProcessUserAvatarForPhoto:photo];
|
||||
[self loadCommentsForPhoto:photo];
|
||||
[self reverseGeocodeLocationForPhoto:photo];
|
||||
}
|
||||
|
||||
- (void)loadCommentsForPhoto:(PhotoModel *)photo
|
||||
{
|
||||
if (photo.commentFeed.numberOfItemsInFeed > 0) {
|
||||
[_photoCommentsView updateWithCommentFeedModel:photo.commentFeed];
|
||||
|
||||
CGRect frame = _photoCommentsView.frame;
|
||||
CGFloat availableWidth = (self.bounds.size.width - HORIZONTAL_BUFFER * 2);
|
||||
frame.size.width = availableWidth;
|
||||
frame.size.height = [CommentView heightForCommentFeedModel:photo.commentFeed withWidth:availableWidth];
|
||||
_photoCommentsView.frame = frame;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)downloadAndProcessUserAvatarForPhoto:(PhotoModel *)photo
|
||||
{
|
||||
[_userAvatarImageView pin_setImageFromURL:_photoModel.ownerUserProfile.userPicURL processorKey:@"custom" processor:^UIImage * _Nullable(PINRemoteImageManagerResult * _Nonnull result, NSUInteger * _Nonnull cost) {
|
||||
CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
|
||||
return [result.image makeCircularImageWithSize:profileImageSize];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reverseGeocodeLocationForPhoto:(PhotoModel *)photo
|
||||
{
|
||||
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
|
||||
|
||||
// check and make sure this is still relevant for this cell (and not an old cell)
|
||||
// make sure to use _photoModel instance variable as photo may change when cell is reused,
|
||||
// where as local variable will never change
|
||||
if (locationModel == _photoModel.location) {
|
||||
_photoLocationLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_photoLocationLabel sizeToFit];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startDownloadingLikesForPhoto:_photoModel
|
||||
{
|
||||
// [_photoModel.likesFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) {
|
||||
//
|
||||
// NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo];
|
||||
// PhotoTableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:rowNum inSection:0]];
|
||||
//
|
||||
// if (cell) {
|
||||
// [cell loadCommentsForPhoto:photo];
|
||||
// [self.tableView beginUpdates];
|
||||
// [self.tableView endUpdates];
|
||||
// // FIXME: adjust content offset - iterate over cells above to get heights...
|
||||
// }
|
||||
// }];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Gesture Handling
|
||||
|
||||
- (void)cellWasLongPressed:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateBegan) {
|
||||
|
||||
// determine which area of cell was tapped
|
||||
CGPoint tapPoint = [sender locationInView:_photoImageView];
|
||||
|
||||
if (tapPoint.y > 0) {
|
||||
|
||||
// photo long pressed
|
||||
NSLog(@"LONG PRESS");
|
||||
|
||||
// need a 2nd method to be able to pass photo model
|
||||
[self longPressRecognized];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)longPressRecognized
|
||||
{
|
||||
[self.delegate cellWasLongPressedWithPhoto:_photoModel];
|
||||
}
|
||||
|
||||
- (void)cellWasTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
// determine which area of cell was tapped
|
||||
CGPoint tapPoint = [sender locationInView:self];
|
||||
|
||||
if (tapPoint.y > HEADER_HEIGHT && tapPoint.y < (HEADER_HEIGHT + self.bounds.size.width)) {
|
||||
|
||||
// photo tapped
|
||||
NSLog(@"TAP: photo");
|
||||
|
||||
} else if (tapPoint.y > (HEADER_HEIGHT + self.bounds.size.width)) {
|
||||
|
||||
[self.delegate photoLikesWasTouchedWithPhoto:_photoModel];
|
||||
[_photoModel.commentFeed requestPageWithCompletionBlock:^(NSArray *newcomments) {}]; //FIXME: make comments likes :)
|
||||
|
||||
// photo tapped
|
||||
NSLog(@"TAP: photo likes");
|
||||
|
||||
} else if (tapPoint.x <= CGRectGetMaxX(_userAvatarImageView.frame)) {
|
||||
|
||||
// user avatar tapped
|
||||
NSLog(@"TAP: Buddy Icon");
|
||||
|
||||
[self.delegate userProfileWasTouchedWithUser:_photoModel.ownerUserProfile];
|
||||
|
||||
// start downloading likes
|
||||
[self startDownloadingLikesForPhoto:_photoModel];
|
||||
|
||||
} else if (tapPoint.x < CGRectGetMinX(_photoTimeIntervalSincePostLabel.frame)) {
|
||||
|
||||
// check if location exists
|
||||
if (_photoLocationLabel.attributedText) {
|
||||
|
||||
if (tapPoint.y > CGRectGetMinY(_photoLocationLabel.frame)) {
|
||||
NSLog(@"TAP: Location Label");
|
||||
[self.delegate photoLocationWasTouchedWithCoordinate:_photoModel.location.coordinates name:_photoLocationLabel.attributedText];
|
||||
|
||||
} else {
|
||||
|
||||
NSLog(@"Tap: Username Label");
|
||||
|
||||
[self.delegate userProfileWasTouchedWithUser:_photoModel.ownerUserProfile];
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// username tapped
|
||||
NSLog(@"Tap: Username Label");
|
||||
|
||||
[self.delegate userProfileWasTouchedWithUser:_photoModel.ownerUserProfile];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
15
examples/ASDKgram/Sample/PhotoTableViewController.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// PhotoTableViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface PhotoTableViewController : UITableViewController
|
||||
|
||||
- (void)refreshFeed;
|
||||
|
||||
@end
|
||||
273
examples/ASDKgram/Sample/PhotoTableViewController.m
Normal file
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// PhotoTableViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/17/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PhotoTableViewController.h"
|
||||
#import "PhotoTableViewCell.h"
|
||||
#import "UserProfileViewController.h"
|
||||
#import "LikersViewController.h"
|
||||
#import "LocationCollectionViewController.h"
|
||||
#import "PhotoFeedModel.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
|
||||
#define AUTO_TAIL_LOADING_NUM_SCREENFULS 2.5
|
||||
|
||||
@interface PhotoTableViewController () <PhotoTableViewCellProtocol>
|
||||
@end
|
||||
|
||||
@implementation PhotoTableViewController
|
||||
{
|
||||
PhotoFeedModel *_photoFeed;
|
||||
UIView *_statusBarOpaqueUnderlayView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewStyle)style
|
||||
{
|
||||
self = [super initWithStyle:style];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.navigationItem.title = @"500pixgram";
|
||||
[self.navigationController setNavigationBarHidden:YES];
|
||||
|
||||
self.refreshControl = [[UIRefreshControl alloc] init];
|
||||
[self.refreshControl addTarget:self action:@selector(refreshFeed) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
_photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]];
|
||||
[self refreshFeed];
|
||||
|
||||
_statusBarOpaqueUnderlayView = [[UIView alloc] init];
|
||||
_statusBarOpaqueUnderlayView.backgroundColor = [UIColor darkBlueColor];
|
||||
[[[UIApplication sharedApplication] keyWindow] addSubview:_statusBarOpaqueUnderlayView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad // anything involving the view should go here, not init
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.tableView.allowsSelection = NO;
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[self.tableView registerClass:[PhotoTableViewCell class] forCellReuseIdentifier:@"photoCell"];
|
||||
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.navigationController.hidesBarsOnSwipe = YES;
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
_statusBarOpaqueUnderlayView.frame = [[UIApplication sharedApplication] statusBarFrame];
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (void)refreshFeed
|
||||
{
|
||||
// small first batch
|
||||
[_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){
|
||||
|
||||
[self.tableView reloadData]; // overwrite tableView instead of inserting new rows
|
||||
[self.refreshControl endRefreshing];
|
||||
[self requestCommentsForPhotos:newPhotos];
|
||||
|
||||
// immediately start second larger fetch
|
||||
[self loadPage];
|
||||
|
||||
} numResultsToReturn:4];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)loadPage
|
||||
{
|
||||
[_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
||||
[self insertNewRowsInTableView:newPhotos];
|
||||
[self requestCommentsForPhotos:newPhotos];
|
||||
} numResultsToReturn:20];
|
||||
}
|
||||
|
||||
- (void)requestCommentsForPhotos:(NSArray *)newPhotos
|
||||
{
|
||||
for (PhotoModel *photo in newPhotos) {
|
||||
|
||||
[photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) {
|
||||
|
||||
NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo];
|
||||
PhotoTableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:rowNum inSection:0]];
|
||||
|
||||
if (cell) {
|
||||
[cell loadCommentsForPhoto:photo];
|
||||
[self.tableView beginUpdates];
|
||||
[self.tableView endUpdates];
|
||||
// FIXME: adjust content offset - iterate over cells above to get heights...
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)imageSizeForScreenWidth
|
||||
{
|
||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
||||
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
||||
return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale);
|
||||
}
|
||||
|
||||
- (void)insertNewRowsInTableView:(NSArray *)newPhotos
|
||||
{
|
||||
// NSLog(@"_photoFeed number of items = %lu (%lu total)", (unsigned long)[_photoFeed numberOfItemsInFeed], (long)[_photoFeed totalNumberOfPhotos]);
|
||||
|
||||
// instead of doing tableView reloadData, use table editing commands
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
|
||||
NSInteger section = 0;
|
||||
NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed];
|
||||
for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) {
|
||||
|
||||
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
[indexPaths addObject:path];
|
||||
}
|
||||
|
||||
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}
|
||||
|
||||
//- (void)logPhotoIDsInPhotoFeed
|
||||
//{
|
||||
// NSLog(@"_photoFeed number of items = %lu", (unsigned long)[_photoFeed numberOfItemsInFeed]);
|
||||
//
|
||||
// for (int i = 0; i < [_photoFeed numberOfItemsInFeed]; i++) {
|
||||
// if (i % 4 == 0 && i > 0) {
|
||||
// NSLog(@"\t-----");
|
||||
// }
|
||||
//
|
||||
//// [_photoFeed return]
|
||||
//// NSString *duplicate = ? @"(DUPLICATE)" : @"";
|
||||
// NSLog(@"\t%@ %@", [[_photoFeed objectAtIndex:i] photoID], @"");
|
||||
// }
|
||||
//}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
|
||||
CGFloat currentOffSetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
|
||||
|
||||
// automatic tail loading
|
||||
CGFloat screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight;
|
||||
if (screenfullsBeforeBottom < AUTO_TAIL_LOADING_NUM_SCREENFULS) {
|
||||
// NSLog(@"AUTOMATIC TAIL LOADING BEGIN");
|
||||
[self loadPage];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [_photoFeed numberOfItemsInFeed];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"photoCell" forIndexPath:indexPath];
|
||||
|
||||
cell.delegate = self;
|
||||
[cell updateCellWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
|
||||
{
|
||||
PhotoModel *photo = [_photoFeed objectAtIndex:indexPath.row];
|
||||
return [PhotoTableViewCell heightForPhotoModel:photo withWidth:self.view.bounds.size.width];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - PhotoTableViewCellProtocol
|
||||
|
||||
- (void)userProfileWasTouchedWithUser:(UserModel *)user;
|
||||
{
|
||||
UserProfileViewController *userProfileView = [[UserProfileViewController alloc] initWithUser:user];
|
||||
[self.navigationController pushViewController:userProfileView animated:YES];
|
||||
|
||||
// force navigationController visible
|
||||
[self.navigationController setNavigationBarHidden:NO];
|
||||
}
|
||||
|
||||
- (void)photoLocationWasTouchedWithCoordinate:(CLLocationCoordinate2D)coordiantes name:(NSAttributedString *)name
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.minimumInteritemSpacing = 1;
|
||||
layout.minimumLineSpacing = 1;
|
||||
|
||||
CGFloat boundsWidth = self.view.bounds.size.width;
|
||||
layout.headerReferenceSize = CGSizeMake(boundsWidth, 200);
|
||||
|
||||
CGFloat photoColumnCount = 3;
|
||||
CGFloat photoSize = (boundsWidth - (photoColumnCount - 1)) / photoColumnCount;
|
||||
layout.itemSize = CGSizeMake(photoSize, photoSize);
|
||||
|
||||
LocationCollectionViewController *locationCVC = [[LocationCollectionViewController alloc] initWithCollectionViewLayout:layout coordinates:coordiantes];
|
||||
locationCVC.navigationItem.title = name.string;
|
||||
|
||||
[self.navigationController pushViewController:locationCVC animated:YES];
|
||||
}
|
||||
|
||||
- (void)cellWasLongPressedWithPhoto:(PhotoModel *)photo
|
||||
{
|
||||
UIAlertAction *savePhotoAction = [UIAlertAction actionWithTitle:@"Save Photo"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
NSLog(@"hi");
|
||||
}];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
|
||||
}];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
[alert addAction:savePhotoAction];
|
||||
[alert addAction:cancelAction];
|
||||
|
||||
[self presentViewController:alert animated:YES completion:^{}];
|
||||
}
|
||||
|
||||
- (void)photoLikesWasTouchedWithPhoto:(PhotoModel *)photo
|
||||
{
|
||||
LikersViewController *vc = [[LikersViewController alloc] initWithPhoto:photo];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
|
||||
// force navigationController visible
|
||||
}
|
||||
|
||||
@end
|
||||
43
examples/ASDKgram/Sample/UserModel.h
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// UserModel.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UserModel : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSDictionary *dictionaryRepresentation;
|
||||
@property (nonatomic, assign, readonly) NSUInteger userID;
|
||||
@property (nonatomic, strong, readonly) NSString *username;
|
||||
@property (nonatomic, strong, readonly) NSString *firstName;
|
||||
@property (nonatomic, strong, readonly) NSString *lastName;
|
||||
@property (nonatomic, strong, readonly) NSString *fullName;
|
||||
@property (nonatomic, strong, readonly) NSString *city;
|
||||
@property (nonatomic, strong, readonly) NSString *state;
|
||||
@property (nonatomic, strong, readonly) NSString *country;
|
||||
@property (nonatomic, strong, readonly) NSString *about;
|
||||
@property (nonatomic, strong, readonly) NSString *domain;
|
||||
@property (nonatomic, strong, readonly) NSURL *userPicURL;
|
||||
@property (nonatomic, assign, readonly) NSUInteger photoCount;
|
||||
@property (nonatomic, assign, readonly) NSUInteger galleriesCount;
|
||||
@property (nonatomic, assign, readonly) NSUInteger affection;
|
||||
@property (nonatomic, assign, readonly) NSUInteger friendsCount;
|
||||
@property (nonatomic, assign, readonly) NSUInteger followersCount;
|
||||
@property (nonatomic, assign, readonly) BOOL following;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size;
|
||||
- (NSAttributedString *)fullNameAttributedStringWithFontSize:(CGFloat)size;
|
||||
|
||||
- (void)fetchAvatarImageWithCompletionBlock:(void(^)(UserModel *, UIImage *))block;
|
||||
|
||||
- (void)downloadCompleteUserDataWithCompletionBlock:(void(^)(UserModel *))block;
|
||||
|
||||
@end
|
||||
183
examples/ASDKgram/Sample/UserModel.m
Normal file
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// UserModel.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/26/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UserModel.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "Utilities.h"
|
||||
|
||||
@implementation UserModel
|
||||
{
|
||||
BOOL _fullUserInfoFetchRequested;
|
||||
BOOL _fullUserInfoFetchDone;
|
||||
void (^_fullUserInfoCompletionBlock)(UserModel *);
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWith500pxPhoto:(NSDictionary *)dictionary
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
_fullUserInfoFetchRequested = NO;
|
||||
_fullUserInfoFetchDone = NO;
|
||||
|
||||
// parse user dictionary
|
||||
[self loadUserDataFromDictionary:dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
return [NSAttributedString attributedStringWithString:self.username
|
||||
fontSize:size
|
||||
color:[UIColor darkBlueColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)fullNameAttributedStringWithFontSize:(CGFloat)size
|
||||
{
|
||||
return [NSAttributedString attributedStringWithString:self.fullName
|
||||
fontSize:size
|
||||
color:[UIColor lightGrayColor]
|
||||
firstWordColor:nil];
|
||||
}
|
||||
|
||||
- (void)fetchAvatarImageWithCompletionBlock:(void(^)(UserModel *, UIImage *))block
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfURL:_userPicURL];
|
||||
UIImage *image = [UIImage imageWithData:data];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (block) {
|
||||
block(self, image);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (void)downloadCompleteUserDataWithCompletionBlock:(void(^)(UserModel *))block;
|
||||
{
|
||||
if (_fullUserInfoFetchDone) {
|
||||
NSAssert(!_fullUserInfoCompletionBlock, @"Should not have a waiting block at this point");
|
||||
|
||||
// complete user info fetch complete - excute completion block
|
||||
if (block) {
|
||||
block(self);
|
||||
}
|
||||
|
||||
} else {
|
||||
NSAssert(!_fullUserInfoCompletionBlock, @"Should not have a waiting block at this point");
|
||||
|
||||
// set completion block
|
||||
_fullUserInfoCompletionBlock = block;
|
||||
|
||||
if (!_fullUserInfoFetchRequested) {
|
||||
|
||||
// if fetch not in progress, beging
|
||||
[self fetchCompleteUserData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@", self.dictionaryRepresentation];
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)fetchCompleteUserData
|
||||
{
|
||||
_fullUserInfoFetchRequested = YES;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// fetch JSON data from server
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/users/show?id=%lu&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC", (unsigned long)_userID];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
|
||||
NSAssert(![NSThread isMainThread], @"Absolutely never call NSData URL loader on main thread"); //FIXME:
|
||||
NSData *data = [NSData dataWithContentsOfURL:url];
|
||||
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
|
||||
// parse JSON data
|
||||
if ([response isKindOfClass:[NSDictionary class]]) {
|
||||
[self loadUserDataFromDictionary:response];
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// execute completion block
|
||||
|
||||
_fullUserInfoFetchDone = YES;
|
||||
|
||||
if (_fullUserInfoCompletionBlock) {
|
||||
_fullUserInfoCompletionBlock(self);
|
||||
|
||||
// IT IS ESSENTIAL to nil the block, as it retains a view controller BECAUSE it uses an instance variable which
|
||||
// means that self is retained. It could continue to live on forever
|
||||
// If we don't release this.
|
||||
_fullUserInfoCompletionBlock = nil;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)loadUserDataFromDictionary:(NSDictionary *)dictionary
|
||||
{
|
||||
NSDictionary *userDictionary = [dictionary objectForKey:@"user"];
|
||||
if (![userDictionary isKindOfClass:[NSDictionary class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_userID = [[self guardJSONElement:[userDictionary objectForKey:@"id"]] integerValue];
|
||||
_username = [[self guardJSONElement:[userDictionary objectForKey:@"username"]] lowercaseString];
|
||||
|
||||
if ([_username isKindOfClass:[NSNumber class]]) {
|
||||
_username = @"Anonymous";
|
||||
}
|
||||
|
||||
_firstName = [self guardJSONElement:[userDictionary objectForKey:@"firstname"]];
|
||||
_lastName = [self guardJSONElement:[userDictionary objectForKey:@"lastname"]];
|
||||
_fullName = [self guardJSONElement:[userDictionary objectForKey:@"fullname"]];
|
||||
_city = [self guardJSONElement:[userDictionary objectForKey:@"city"]];
|
||||
_state = [self guardJSONElement:[userDictionary objectForKey:@"state"]];
|
||||
_country = [self guardJSONElement:[userDictionary objectForKey:@"country"]];
|
||||
_about = [self guardJSONElement:[userDictionary objectForKey:@"about"]];
|
||||
_domain = [self guardJSONElement:[userDictionary objectForKey:@"domain"]];
|
||||
_photoCount = [[self guardJSONElement:[userDictionary objectForKey:@"photos_count"]] integerValue];
|
||||
_galleriesCount = [[self guardJSONElement:[userDictionary objectForKey:@"galleries_count"]] integerValue];
|
||||
_affection = [[self guardJSONElement:[userDictionary objectForKey:@"affection"]] integerValue];
|
||||
_friendsCount = [[self guardJSONElement:[userDictionary objectForKey:@"friends_count"]] integerValue];
|
||||
_followersCount = [[self guardJSONElement:[userDictionary objectForKey:@"followers_count"]] integerValue];
|
||||
_following = [[self guardJSONElement:[userDictionary objectForKey:@"following"]] boolValue];
|
||||
_dictionaryRepresentation = userDictionary;
|
||||
|
||||
NSString *urlString = [self guardJSONElement:[userDictionary objectForKey:@"userpic_url"]];
|
||||
_userPicURL = urlString ? [NSURL URLWithString:urlString] : nil;
|
||||
|
||||
}
|
||||
|
||||
- (id)guardJSONElement:(id)element
|
||||
{
|
||||
return (element == [NSNull null]) ? nil : element;
|
||||
}
|
||||
|
||||
@end
|
||||
20
examples/ASDKgram/Sample/UserProfileViewController.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// UserProfileViewController.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/24/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "UserModel.h"
|
||||
|
||||
@interface UserProfileViewController : UIViewController
|
||||
|
||||
//- (instancetype)initWithMe NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithUser:(UserModel *)user NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
|
||||
|
||||
@end
|
||||
401
examples/ASDKgram/Sample/UserProfileViewController.m
Normal file
@@ -0,0 +1,401 @@
|
||||
//
|
||||
// UserProfileViewController.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/24/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UserProfileViewController.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
#define HEADER_HEIGHT 300
|
||||
#define USER_AVATAR_HEIGHT 70
|
||||
#define HEADER_HORIZONTAL_INSET 15
|
||||
|
||||
#define DEBUG_LAYOUT 0
|
||||
|
||||
@implementation UserProfileViewController
|
||||
{
|
||||
UserModel *_user;
|
||||
|
||||
UIImageView *_avatarImageView;
|
||||
UIButton *_followingStatusBtn;
|
||||
UILabel *_fullNameLabel;
|
||||
UILabel *_aboutLabel;
|
||||
UILabel *_domainLabel;
|
||||
UILabel *_followersCountLabel;
|
||||
UILabel *_followingCountLabel;
|
||||
UILabel *_photoCountLabel;
|
||||
|
||||
BOOL _animating;
|
||||
}
|
||||
|
||||
//- (instancetype)initWithMe
|
||||
//{
|
||||
// UserModel *me = [[UserModel alloc] initWithMe];
|
||||
//
|
||||
// [self initWithUser:me];
|
||||
//}
|
||||
|
||||
- (instancetype)initWithUser:(UserModel *)user
|
||||
{
|
||||
self = [super initWithNibName:nil bundle:nil];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
_followingStatusBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_followingStatusBtn.adjustsImageWhenHighlighted = NO;
|
||||
[_followingStatusBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[_followingStatusBtn setTitleColor:[UIColor lightBlueColor] forState:UIControlStateSelected];
|
||||
[_followingStatusBtn setTitle:@"Follow" forState:UIControlStateNormal];
|
||||
[_followingStatusBtn setTitle:@"Following" forState:UIControlStateSelected];
|
||||
[_followingStatusBtn addTarget:self action:@selector(toggleFollowing) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_followingStatusBtn];
|
||||
|
||||
_fullNameLabel = [[UILabel alloc] init];
|
||||
_fullNameLabel.font = [_fullNameLabel.font fontWithSize:14];
|
||||
[self.view addSubview:_fullNameLabel];
|
||||
|
||||
_aboutLabel = [[UILabel alloc] init];
|
||||
_aboutLabel.font = [_aboutLabel.font fontWithSize:14];
|
||||
_aboutLabel.numberOfLines = 3;
|
||||
[self.view addSubview:_aboutLabel];
|
||||
|
||||
_domainLabel = [[UILabel alloc] init];
|
||||
_domainLabel.font = [_domainLabel.font fontWithSize:14];
|
||||
_domainLabel.textColor = [UIColor darkBlueColor];
|
||||
[self.view addSubview:_domainLabel];
|
||||
|
||||
_followersCountLabel = [[UILabel alloc] init];
|
||||
_followersCountLabel.font = [_followersCountLabel.font fontWithSize:14];
|
||||
_followersCountLabel.textColor = [UIColor lightGrayColor];
|
||||
_followersCountLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_followersCountLabel.numberOfLines = 2;
|
||||
[self.view addSubview:_followersCountLabel];
|
||||
|
||||
_followingCountLabel = [[UILabel alloc] init];
|
||||
_followingCountLabel.font = [_followingCountLabel.font fontWithSize:14];
|
||||
_followingCountLabel.textColor = [UIColor lightGrayColor];
|
||||
_followingCountLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_followingCountLabel.numberOfLines = 2;
|
||||
[self.view addSubview:_followingCountLabel];
|
||||
|
||||
_photoCountLabel = [[UILabel alloc] init];
|
||||
_photoCountLabel.font = [_photoCountLabel.font fontWithSize:14];
|
||||
_photoCountLabel.textColor = [UIColor lightGrayColor];
|
||||
_photoCountLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_photoCountLabel.numberOfLines = 2;
|
||||
[self.view addSubview:_photoCountLabel];
|
||||
|
||||
// add to view last so that animation is on top
|
||||
_avatarImageView = [[UIImageView alloc] init];
|
||||
[self.view addSubview:_avatarImageView];
|
||||
|
||||
// This is what we have available as soon as we're created, without fetching new metadata from the network.
|
||||
_fullNameLabel.text = _user.fullName;
|
||||
_user = user;
|
||||
self.navigationItem.title = [user.username uppercaseString];
|
||||
|
||||
// get full set of user data
|
||||
[self loadAdditionalProfileFields];
|
||||
|
||||
// get avatar image
|
||||
[self loadAvatarImage];
|
||||
|
||||
|
||||
|
||||
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(viewWasLongPressed:)];
|
||||
// [self.view addGestureRecognizer:lpgr];
|
||||
lpgr.minimumPressDuration = 0.01;
|
||||
|
||||
if (DEBUG_LAYOUT) {
|
||||
_avatarImageView.backgroundColor = [UIColor greenColor];
|
||||
_fullNameLabel.backgroundColor = [UIColor greenColor];
|
||||
_aboutLabel.backgroundColor = [UIColor greenColor];
|
||||
_domainLabel.backgroundColor = [UIColor greenColor];
|
||||
_followersCountLabel.backgroundColor = [UIColor greenColor];
|
||||
_followingCountLabel.backgroundColor = [UIColor greenColor];
|
||||
_photoCountLabel.backgroundColor = [UIColor greenColor];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.view.bounds.size;
|
||||
|
||||
if (_animating) {
|
||||
return;
|
||||
}
|
||||
|
||||
// user avatar
|
||||
CGFloat x = HEADER_HORIZONTAL_INSET;
|
||||
CGFloat originalY = CGRectGetMaxY(self.navigationController.navigationBar.frame) + HEADER_HORIZONTAL_INSET;
|
||||
CGFloat y = originalY;
|
||||
_avatarImageView.frame = CGRectMake(x,
|
||||
y,
|
||||
USER_AVATAR_HEIGHT,
|
||||
USER_AVATAR_HEIGHT);
|
||||
y += _avatarImageView.frame.size.height;
|
||||
|
||||
if (!_avatarImageView.image) {
|
||||
// We generate the rounded image at layout time (unusually late) so that we can know exactly how large
|
||||
// it needs to be. This is the only way to ensure the rounded curve is perfectly smoothed / antialiased.
|
||||
// _avatarImageView.image = [avatar makeCircularImageWithSize:CGSizeMake(USER_AVATAR_HEIGHT, USER_AVATAR_HEIGHT)];
|
||||
}
|
||||
|
||||
if (_fullNameLabel.text) {
|
||||
[_fullNameLabel sizeToFit];
|
||||
y += HEADER_HORIZONTAL_INSET / 2.0;
|
||||
_fullNameLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
boundsSize.width - 2 * HEADER_HORIZONTAL_INSET,
|
||||
_fullNameLabel.frame.size.height);
|
||||
|
||||
y += _fullNameLabel.frame.size.height;
|
||||
}
|
||||
|
||||
if (_aboutLabel.text) {
|
||||
[_aboutLabel sizeToFit];
|
||||
y += HEADER_HORIZONTAL_INSET / 2.0;
|
||||
_aboutLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
boundsSize.width - 2 * HEADER_HORIZONTAL_INSET,
|
||||
_aboutLabel.frame.size.height);
|
||||
|
||||
y += _aboutLabel.frame.size.height;
|
||||
}
|
||||
|
||||
if (_domainLabel.text) {
|
||||
[_domainLabel sizeToFit];
|
||||
y += HEADER_HORIZONTAL_INSET / 2.0;
|
||||
_domainLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
boundsSize.width - 2 * HEADER_HORIZONTAL_INSET,
|
||||
_domainLabel.frame.size.height);
|
||||
}
|
||||
|
||||
CGFloat availableWidth = boundsSize.width - 3 * HEADER_HORIZONTAL_INSET - USER_AVATAR_HEIGHT;
|
||||
CGFloat actualWidth = floorf((availableWidth - 2 * HEADER_HORIZONTAL_INSET / 2.0) / 3.0);
|
||||
|
||||
y = originalY;
|
||||
x += USER_AVATAR_HEIGHT + HEADER_HORIZONTAL_INSET;
|
||||
|
||||
[_photoCountLabel sizeToFit];
|
||||
_photoCountLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
actualWidth,
|
||||
_photoCountLabel.frame.size.height);
|
||||
x += actualWidth;
|
||||
|
||||
[_followersCountLabel sizeToFit];
|
||||
x += HEADER_HORIZONTAL_INSET / 2.0;
|
||||
_followersCountLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
actualWidth,
|
||||
_followersCountLabel.frame.size.height);
|
||||
x += actualWidth;
|
||||
|
||||
[_followingCountLabel sizeToFit];
|
||||
x += HEADER_HORIZONTAL_INSET / 2.0;
|
||||
_followingCountLabel.frame = CGRectMake(x,
|
||||
y,
|
||||
actualWidth,
|
||||
_followingCountLabel.frame.size.height);
|
||||
|
||||
x = USER_AVATAR_HEIGHT + 2 * HEADER_HORIZONTAL_INSET;
|
||||
y = originalY + USER_AVATAR_HEIGHT / 2.0;
|
||||
_followingStatusBtn.frame = CGRectMake(x,
|
||||
y,
|
||||
availableWidth,
|
||||
USER_AVATAR_HEIGHT / 2.0);
|
||||
|
||||
[self setFollowingButtonBackgroundsForRect:_followingStatusBtn.frame.size];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Touch Events
|
||||
|
||||
- (void)viewWasLongPressed:(UIGestureRecognizer *)sender
|
||||
{
|
||||
|
||||
if (sender.state == UIGestureRecognizerStateBegan) {
|
||||
|
||||
// determine which area of cell was tapped
|
||||
CGPoint tapPoint = [sender locationInView:_avatarImageView];
|
||||
|
||||
if (tapPoint.y > 0) {
|
||||
|
||||
NSLog(@"LONG PRESS STARTED");
|
||||
|
||||
_animating = YES;
|
||||
|
||||
// FIXME: use pinremote image to download higher res photo
|
||||
|
||||
[UIView animateWithDuration:0.2
|
||||
delay:0
|
||||
usingSpringWithDamping:0.5
|
||||
initialSpringVelocity:1.0f
|
||||
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
|
||||
animations:^{
|
||||
|
||||
// FIXME: grey the background
|
||||
|
||||
_avatarImageView.frame = CGRectMake(100, 100, 100, 100);
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
_animating = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.state == UIGestureRecognizerStateEnded) {
|
||||
|
||||
// determine which area of cell was tapped
|
||||
CGPoint tapPoint = [sender locationInView:_avatarImageView];
|
||||
|
||||
if (tapPoint.y > 0) { // FIXME: all long presses work here
|
||||
|
||||
NSLog(@"LONG PRESS ENDED");
|
||||
|
||||
_animating = YES;
|
||||
|
||||
[UIView animateWithDuration:0.2
|
||||
delay:0
|
||||
usingSpringWithDamping:0.5
|
||||
initialSpringVelocity:1.0f
|
||||
options: UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
|
||||
animations:^{
|
||||
|
||||
CGFloat x = HEADER_HORIZONTAL_INSET;
|
||||
CGFloat originalY = CGRectGetMaxY(self.navigationController.navigationBar.frame) + HEADER_HORIZONTAL_INSET;
|
||||
CGFloat y = originalY;
|
||||
_avatarImageView.frame = CGRectMake(x,
|
||||
y,
|
||||
USER_AVATAR_HEIGHT,
|
||||
USER_AVATAR_HEIGHT);
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
_animating = NO;
|
||||
|
||||
}];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleFollowing
|
||||
{
|
||||
// toggle button state
|
||||
if (_followingStatusBtn.selected) {
|
||||
|
||||
// stop following
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/users/%lu/friends?consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC", (long)_user.userID];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request setHTTPMethod:@"DELETE"];
|
||||
|
||||
// FIXME: update user model
|
||||
// FIXME: check for success
|
||||
|
||||
} else {
|
||||
|
||||
// start following
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/users/%lu/friends?consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC", (long)_user.userID];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
}
|
||||
|
||||
_followingStatusBtn.selected = !_followingStatusBtn.selected;
|
||||
}
|
||||
|
||||
- (void)getFollowers
|
||||
{
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://api.500px.com/v1/users/%lu/friends?consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC", (long)_user.userID];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request setHTTPMethod:@"GET"];
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)loadAdditionalProfileFields
|
||||
{
|
||||
// fetch full user profile info
|
||||
[_user downloadCompleteUserDataWithCompletionBlock:^(UserModel *userModel) {
|
||||
|
||||
// check that info returning from async download is still applicable to this view
|
||||
if (userModel == _user) {
|
||||
|
||||
_followingStatusBtn.selected = userModel.following;
|
||||
_followersCountLabel.text = [NSString stringWithFormat:@"%lu\nfollowers", (long)userModel.followersCount];
|
||||
_followingCountLabel.text = [NSString stringWithFormat:@"%lu\nfollowing", (long)userModel.friendsCount];
|
||||
_photoCountLabel.text = [NSString stringWithFormat:@"%lu\nphotos", (long)userModel.photoCount];
|
||||
_aboutLabel.text = userModel.about;
|
||||
_domainLabel.text = userModel.domain;
|
||||
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)loadAvatarImage
|
||||
{
|
||||
[_user fetchAvatarImageWithCompletionBlock:^(UserModel *userModel, UIImage *avatar) {
|
||||
|
||||
// check that info returning from async download is still applicable to this view
|
||||
if (userModel == _user) {
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#define FOLLOW_BUTTON_CORNER_RADIUS 8
|
||||
- (void)setFollowingButtonBackgroundsForRect:(CGSize)size
|
||||
{
|
||||
CGSize unstretchedSize = CGSizeMake(2 * FOLLOW_BUTTON_CORNER_RADIUS + 1, 2 * FOLLOW_BUTTON_CORNER_RADIUS + 1);
|
||||
CGRect rect = (CGRect) {CGPointZero, unstretchedSize};
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:FOLLOW_BUTTON_CORNER_RADIUS];
|
||||
|
||||
UIColor *lightBlue = [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0];
|
||||
|
||||
// create a graphics context for the following status button
|
||||
UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
|
||||
|
||||
[path addClip];
|
||||
[lightBlue setFill];
|
||||
[path fill];
|
||||
|
||||
UIImage *notFollowingBtnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
UIImage *notFollowingBtnImageStretchable = [notFollowingBtnImage stretchableImageWithLeftCapWidth:FOLLOW_BUTTON_CORNER_RADIUS topCapHeight:FOLLOW_BUTTON_CORNER_RADIUS];
|
||||
[_followingStatusBtn setBackgroundImage:notFollowingBtnImageStretchable forState:UIControlStateNormal];
|
||||
|
||||
// create a graphics context for the not yet following status button
|
||||
UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
|
||||
|
||||
[path addClip];
|
||||
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
|
||||
path.lineWidth = 3;
|
||||
[lightBlue setStroke];
|
||||
[path stroke];
|
||||
|
||||
UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:FOLLOW_BUTTON_CORNER_RADIUS topCapHeight:FOLLOW_BUTTON_CORNER_RADIUS];
|
||||
[_followingStatusBtn setBackgroundImage:followingBtnImageStretchable forState:UIControlStateSelected];
|
||||
}
|
||||
|
||||
@end
|
||||
31
examples/ASDKgram/Sample/UserRowView.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// UserRowView.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/13/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "UserModel.h"
|
||||
#import "PhotoModel.h"
|
||||
|
||||
|
||||
typedef NS_ENUM(NSInteger, UserRowViewType) {
|
||||
UserRowViewTypeLikes,
|
||||
UserRowViewTypeComments,
|
||||
UserRowViewTypePhotoCell
|
||||
};
|
||||
|
||||
@interface UserRowView : UIView
|
||||
|
||||
+ (CGFloat)heightForUserRowViewType:(UserRowViewType)type;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame withPhotoFeedModelType:(UserRowViewType)type NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)updateWithPhotoModel:(PhotoModel *)photo;
|
||||
- (void)updateWithCommentModel:(CommentModel *)comment;
|
||||
|
||||
@end
|
||||
228
examples/ASDKgram/Sample/UserRowView.m
Normal file
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// UserRowView.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/13/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UserRowView.h"
|
||||
#import "PINImageView+PINRemoteImage.h"
|
||||
#import "PINButton+PINRemoteImage.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
#define LIKES_VIEW_HEIGHT 50
|
||||
#define LIKES_IMAGE_HEIGHT 30
|
||||
|
||||
#define PHOTOCELL_VIEW_HEIGHT 50
|
||||
#define PHOTOCELL_IMAGE_HEIGHT 30
|
||||
|
||||
#define HORIZONTAL_BUFFER 10
|
||||
#define VERTICAL_BUFFER 5
|
||||
#define FONT_SIZE 14
|
||||
|
||||
#define FOLLOW_BUTTON_CORNER_RADIUS 8
|
||||
|
||||
@implementation UserRowView
|
||||
{
|
||||
UserRowViewType _viewType;
|
||||
PhotoModel *_photo;
|
||||
CommentModel *_comment;
|
||||
UIImageView *_userAvatarImageView;
|
||||
UIButton *_followingStatusBtn;
|
||||
UILabel *_userNameLabel;
|
||||
UILabel *_detailLabel; // configurable to be location, comment, full name
|
||||
UILabel *_photoTimeIntervalSincePostLabel;
|
||||
}
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
+ (CGFloat)heightForUserRowViewType:(UserRowViewType)type
|
||||
{
|
||||
if (type && UserRowViewTypeLikes) {
|
||||
return LIKES_VIEW_HEIGHT;
|
||||
} else {
|
||||
return LIKES_IMAGE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame withPhotoFeedModelType:(UserRowViewType)type
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
|
||||
_viewType = type;
|
||||
_userAvatarImageView = [[UIImageView alloc] init];
|
||||
[_userAvatarImageView setPin_updateWithProgress:YES];
|
||||
_userNameLabel = [[UILabel alloc] init];
|
||||
_detailLabel = [[UILabel alloc] init];
|
||||
[self addSubview:_userAvatarImageView];
|
||||
[self addSubview:_userNameLabel];
|
||||
[self addSubview:_detailLabel];
|
||||
|
||||
if (type == UserRowViewTypeLikes) {
|
||||
|
||||
_followingStatusBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[self addSubview:_followingStatusBtn];
|
||||
|
||||
UIImage *followingImage = [UIImage followingButtonStretchableImageForCornerRadius:FOLLOW_BUTTON_CORNER_RADIUS following:YES];
|
||||
UIImage *notFollowingImage = [UIImage followingButtonStretchableImageForCornerRadius:FOLLOW_BUTTON_CORNER_RADIUS following:NO];
|
||||
[_followingStatusBtn setBackgroundImage:followingImage forState:UIControlStateSelected];
|
||||
[_followingStatusBtn setBackgroundImage:notFollowingImage forState:UIControlStateNormal];
|
||||
|
||||
} else {
|
||||
|
||||
_photoTimeIntervalSincePostLabel = [[UILabel alloc] init];
|
||||
[self addSubview:_photoTimeIntervalSincePostLabel];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.bounds.size;
|
||||
CGFloat viewHeight = (_viewType && UserRowViewTypeLikes) ? LIKES_VIEW_HEIGHT : PHOTOCELL_VIEW_HEIGHT;
|
||||
CGFloat avatarHeight = (_viewType && UserRowViewTypeLikes) ? LIKES_IMAGE_HEIGHT : PHOTOCELL_IMAGE_HEIGHT;
|
||||
|
||||
CGRect rect = CGRectMake(HORIZONTAL_BUFFER, (viewHeight - avatarHeight) / 2.0, avatarHeight, avatarHeight);
|
||||
_userAvatarImageView.frame = rect;
|
||||
|
||||
if (_viewType == UserRowViewTypeLikes) {
|
||||
|
||||
rect.size = _followingStatusBtn.bounds.size;
|
||||
rect.origin.x = boundsSize.width - HORIZONTAL_BUFFER - rect.size.width;
|
||||
rect.origin.y = (viewHeight - rect.size.height) / 2.0;
|
||||
_followingStatusBtn.frame = rect;
|
||||
|
||||
} else {
|
||||
|
||||
rect.size = _photoTimeIntervalSincePostLabel.bounds.size;
|
||||
rect.origin.x = boundsSize.width - HORIZONTAL_BUFFER - rect.size.width;
|
||||
rect.origin.y = (viewHeight - rect.size.height) / 2.0;
|
||||
_photoTimeIntervalSincePostLabel.frame = rect;
|
||||
}
|
||||
|
||||
CGFloat availableWidth = CGRectGetMinX(rect) - HORIZONTAL_BUFFER;
|
||||
rect.size = _userNameLabel.bounds.size;
|
||||
rect.size.width = MIN(availableWidth, rect.size.width);
|
||||
rect.origin.x = HORIZONTAL_BUFFER + avatarHeight + HORIZONTAL_BUFFER;
|
||||
|
||||
if (_detailLabel.attributedText) {
|
||||
CGSize locationSize = _userNameLabel.bounds.size;
|
||||
locationSize.width = MIN(availableWidth, locationSize.width);
|
||||
|
||||
rect.origin.y = (viewHeight - rect.size.height - locationSize.height) / 2.0;
|
||||
_userNameLabel.frame = rect;
|
||||
|
||||
// FIXME: Name rects at least for this sub-condition
|
||||
rect.origin.y += rect.size.height;
|
||||
rect.size = locationSize;
|
||||
_detailLabel.frame = rect;
|
||||
|
||||
} else {
|
||||
rect.origin.y = (viewHeight - rect.size.height) / 2.0;
|
||||
_userNameLabel.frame = rect;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithPhotoModel:(PhotoModel *)photo
|
||||
{
|
||||
[self clearFields];
|
||||
|
||||
_photo = photo;
|
||||
_userNameLabel.attributedText = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_userNameLabel sizeToFit];
|
||||
|
||||
// [self downloadAndProcessUserAvatarForPhoto:photo];
|
||||
|
||||
switch (_viewType) {
|
||||
case UserRowViewTypeLikes:
|
||||
|
||||
_detailLabel.attributedText = [photo.ownerUserProfile fullNameAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_detailLabel sizeToFit];
|
||||
|
||||
_followingStatusBtn.selected = YES; // FIXME:
|
||||
_followingStatusBtn.frame = CGRectMake(0, 0, 20, 30); // FIXME:
|
||||
break;
|
||||
|
||||
case UserRowViewTypePhotoCell:
|
||||
[self reverseGeocodeLocationForPhoto:photo];
|
||||
_photoTimeIntervalSincePostLabel.attributedText = [photo uploadDateAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_photoTimeIntervalSincePostLabel sizeToFit];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)updateWithCommentModel:(CommentModel *)comment
|
||||
{
|
||||
[self clearFields];
|
||||
|
||||
_comment = comment;
|
||||
_userNameLabel.attributedText = [[NSAttributedString alloc] initWithString:comment.commenterUsername]; // FIXME:
|
||||
[_userNameLabel sizeToFit];
|
||||
|
||||
_detailLabel.attributedText = [comment commentAttributedString]; //FIXME: add userModel to commentModel? don't include user name!!!
|
||||
[_detailLabel sizeToFit];
|
||||
|
||||
_photoTimeIntervalSincePostLabel.attributedText = [comment uploadDateAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_photoTimeIntervalSincePostLabel sizeToFit];
|
||||
|
||||
[self downloadAndProcessUserAvatarForURLString:comment.commenterAvatarURL];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)downloadAndProcessUserAvatarForURLString:(NSString *)urlString
|
||||
{
|
||||
CGFloat avatarHeight = (_viewType == UserRowViewTypeLikes) ? LIKES_IMAGE_HEIGHT : PHOTOCELL_IMAGE_HEIGHT;
|
||||
|
||||
[_userAvatarImageView pin_setImageFromURL:[NSURL URLWithString:urlString] processorKey:@"custom" processor:^UIImage * _Nullable(PINRemoteImageManagerResult * _Nonnull result, NSUInteger * _Nonnull cost) {
|
||||
CGSize profileImageSize = CGSizeMake(avatarHeight, avatarHeight);
|
||||
return [result.image makeCircularImageWithSize:profileImageSize];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reverseGeocodeLocationForPhoto:(PhotoModel *)photo
|
||||
{
|
||||
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
|
||||
|
||||
// check and make sure this is still relevant for this cell (and not an old cell)
|
||||
// make sure to use _photoModel instance variable as photo may change when cell is reused,
|
||||
// where as local variable will never change
|
||||
if (locationModel == _photo.location) {
|
||||
_detailLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
[_detailLabel sizeToFit];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)clearFields
|
||||
{
|
||||
_photo = nil;
|
||||
_comment = nil;
|
||||
_userAvatarImageView.image = nil;
|
||||
_userNameLabel.attributedText = nil;
|
||||
_detailLabel.attributedText = nil;
|
||||
_photoTimeIntervalSincePostLabel.attributedText = nil;
|
||||
_followingStatusBtn.frame = CGRectZero;
|
||||
_photoTimeIntervalSincePostLabel.frame = CGRectZero;
|
||||
}
|
||||
|
||||
@end
|
||||
39
examples/ASDKgram/Sample/Utilities.h
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Utilities.h
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIColor (Additions)
|
||||
|
||||
+ (UIColor *)darkBlueColor;
|
||||
+ (UIColor *)lightBlueColor;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIImage (Additions)
|
||||
|
||||
+ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled;
|
||||
|
||||
- (UIImage *)makeCircularImageWithSize:(CGSize)size;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSString (Additions)
|
||||
|
||||
// returns a user friendly elapsed time such as '50s', '6m' or '3w'
|
||||
+ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSAttributedString (Additions)
|
||||
|
||||
+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size
|
||||
color:(UIColor *)color firstWordColor:(UIColor *)firstWordColor;
|
||||
|
||||
@end
|
||||
188
examples/ASDKgram/Sample/Utilities.m
Normal file
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Utilities.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 3/9/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Utilities.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#define StrokeRoundedImages 0
|
||||
|
||||
@implementation UIColor (Additions)
|
||||
|
||||
+ (UIColor *)darkBlueColor
|
||||
{
|
||||
return [UIColor colorWithRed:18.0/255.0 green:86.0/255.0 blue:136.0/255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
+ (UIColor *)lightBlueColor
|
||||
{
|
||||
return [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIImage (Additions)
|
||||
|
||||
+ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled
|
||||
{
|
||||
CGSize unstretchedSize = CGSizeMake(2 * cornerRadius + 1, 2 * cornerRadius + 1);
|
||||
CGRect rect = (CGRect) {CGPointZero, unstretchedSize};
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
|
||||
|
||||
// create a graphics context for the following status button
|
||||
UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
|
||||
|
||||
[path addClip];
|
||||
|
||||
if (followingEnabled) {
|
||||
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
|
||||
path.lineWidth = 3;
|
||||
[[UIColor lightBlueColor] setStroke];
|
||||
[path stroke];
|
||||
|
||||
} else {
|
||||
|
||||
[[UIColor lightBlueColor] setFill];
|
||||
[path fill];
|
||||
}
|
||||
|
||||
UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:cornerRadius
|
||||
topCapHeight:cornerRadius];
|
||||
return followingBtnImageStretchable;
|
||||
}
|
||||
|
||||
- (UIImage *)makeCircularImageWithSize:(CGSize)size
|
||||
{
|
||||
// make a CGRect with the image's size
|
||||
CGRect circleRect = (CGRect) {CGPointZero, size};
|
||||
|
||||
// begin the image context since we're not in a drawRect:
|
||||
UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0);
|
||||
|
||||
// create a UIBezierPath circle
|
||||
UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2];
|
||||
|
||||
// clip to the circle
|
||||
[circle addClip];
|
||||
|
||||
// draw the image in the circleRect *AFTER* the context is clipped
|
||||
[self drawInRect:circleRect];
|
||||
|
||||
// create a border (for white background pictures)
|
||||
#if StrokeRoundedImages
|
||||
circle.lineWidth = 1;
|
||||
[[UIColor darkGrayColor] set];
|
||||
[circle stroke];
|
||||
#endif
|
||||
|
||||
// get an image from the image context
|
||||
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
// end the image context since we're not in a drawRect:
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return roundedImage;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (Additions)
|
||||
|
||||
// Returns a user-visible date time string that corresponds to the
|
||||
// specified RFC 3339 date time string. Note that this does not handle
|
||||
// all possible RFC 3339 date time strings, just one of the most common
|
||||
// styles.
|
||||
+ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString
|
||||
{
|
||||
NSDateFormatter * rfc3339DateFormatter;
|
||||
NSLocale * enUSPOSIXLocale;
|
||||
|
||||
// Convert the RFC 3339 date time string to an NSDate.
|
||||
|
||||
rfc3339DateFormatter = [[NSDateFormatter alloc] init];
|
||||
|
||||
enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
|
||||
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
|
||||
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"];
|
||||
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
|
||||
return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];
|
||||
}
|
||||
|
||||
+ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString
|
||||
{
|
||||
// early return if no post date string
|
||||
if (!uploadDateString)
|
||||
{
|
||||
return @"NO POST DATE";
|
||||
}
|
||||
|
||||
NSDate *postDate = [self userVisibleDateTimeStringForRFC3339DateTimeString:uploadDateString];
|
||||
|
||||
if (!postDate) {
|
||||
return @"DATE CONVERSION ERROR";
|
||||
}
|
||||
|
||||
NSDate *currentDate = [NSDate date];
|
||||
|
||||
NSCalendar *calendar = [NSCalendar currentCalendar];
|
||||
|
||||
NSUInteger seconds = [[calendar components:NSCalendarUnitSecond fromDate:postDate toDate:currentDate options:0] second];
|
||||
NSUInteger minutes = [[calendar components:NSCalendarUnitMinute fromDate:postDate toDate:currentDate options:0] minute];
|
||||
NSUInteger hours = [[calendar components:NSCalendarUnitHour fromDate:postDate toDate:currentDate options:0] hour];
|
||||
NSUInteger days = [[calendar components:NSCalendarUnitDay fromDate:postDate toDate:currentDate options:0] day];
|
||||
|
||||
NSString *elapsedTime;
|
||||
|
||||
if (days > 7) {
|
||||
elapsedTime = [NSString stringWithFormat:@"%luw", (long)ceil(days/7.0)];
|
||||
} else if (days > 0) {
|
||||
elapsedTime = [NSString stringWithFormat:@"%lud", (long)days];
|
||||
} else if (hours > 0) {
|
||||
elapsedTime = [NSString stringWithFormat:@"%luh", (long)hours];
|
||||
} else if (minutes > 0) {
|
||||
elapsedTime = [NSString stringWithFormat:@"%lum", (long)minutes];
|
||||
} else if (seconds > 0) {
|
||||
elapsedTime = [NSString stringWithFormat:@"%lus", (long)seconds];
|
||||
} else if (seconds == 0) {
|
||||
elapsedTime = @"1s";
|
||||
} else {
|
||||
elapsedTime = @"ERROR";
|
||||
}
|
||||
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSAttributedString (Additions)
|
||||
|
||||
+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size
|
||||
color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor
|
||||
{
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor],
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:size]};
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
|
||||
[attributedString addAttributes:attributes range:NSMakeRange(0, string.length)];
|
||||
|
||||
if (firstWordColor) {
|
||||
NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location);
|
||||
[attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange];
|
||||
}
|
||||
|
||||
return attributedString;
|
||||
}
|
||||
|
||||
@end
|
||||
21
examples/ASDKgram/Sample/main.m
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// main.m
|
||||
// Flickrgram
|
||||
//
|
||||
// Created by Hannah Troisi on 2/16/16.
|
||||
// Copyright © 2016 Hannah Troisi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
BIN
examples/ASDKgram/Sample/tabBarIcons/camera.png
Normal file
|
After Width: | Height: | Size: 483 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/camera@2x.png
Normal file
|
After Width: | Height: | Size: 822 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/cameraRaw.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
examples/ASDKgram/Sample/tabBarIcons/earth.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/ASDKgram/Sample/tabBarIcons/earth@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
examples/ASDKgram/Sample/tabBarIcons/home.png
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/home@2x.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/homeRaw.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
examples/ASDKgram/Sample/tabBarIcons/profile.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/profile@2x.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
examples/ASDKgram/Sample/tabBarIcons/profileRaw.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
examples/ASDKgram/tabBarIcons/camera.png
Normal file
|
After Width: | Height: | Size: 483 B |
BIN
examples/ASDKgram/tabBarIcons/camera@2x.png
Normal file
|
After Width: | Height: | Size: 822 B |
BIN
examples/ASDKgram/tabBarIcons/cameraRaw.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
examples/ASDKgram/tabBarIcons/earth.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/ASDKgram/tabBarIcons/earth@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
examples/ASDKgram/tabBarIcons/home.png
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
examples/ASDKgram/tabBarIcons/home@2x.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
examples/ASDKgram/tabBarIcons/homeRaw.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
examples/ASDKgram/tabBarIcons/profile.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
examples/ASDKgram/tabBarIcons/profile@2x.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
examples/ASDKgram/tabBarIcons/profileRaw.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |