├── .gitignore ├── ADBFeedReader.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── ADBFeedReader.xcscheme ├── ADBFeedReader.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── ADBFeedReader.xccheckout ├── ADBFeedReader ├── ADBAppDelegate.h ├── ADBAppDelegate.m ├── ADBConstants.h ├── ADBDetailTableViewController.h ├── ADBDetailTableViewController.m ├── ADBDetailTableViewController.xib ├── ADBFeedInfoDTO.h ├── ADBFeedInfoDTO.m ├── ADBFeedItemDTO.h ├── ADBFeedItemDTO.m ├── ADBFeedParser.h ├── ADBFeedParser.m ├── ADBFeedParserProtocol.h ├── ADBFeedReader-Info.plist ├── ADBFeedReader-Prefix.pch ├── ADBImageView.h ├── ADBImageView.m ├── ADBMacros.h ├── ADBMasterTableViewController.h ├── ADBMasterTableViewController.m ├── ADBMasterTableViewController.xib ├── ADBWebBrowserViewController.h ├── ADBWebBrowserViewController.m ├── ADBWebBrowserViewController_iPad.xib ├── ADBWebBrowserViewController_iPhone.xib ├── FeedInfo+Additions.h ├── FeedInfo+Additions.m ├── FeedInfo.h ├── FeedInfo.m ├── FeedItem+Additions.h ├── FeedItem+Additions.m ├── FeedItem.h ├── FeedItem.m ├── FeedReader.xcdatamodeld │ ├── .xccurrentversion │ └── FeedReader.xcdatamodel │ │ └── contents ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── NSDate+InternetDateTime.h ├── NSDate+InternetDateTime.m ├── NSString+HTML.h ├── NSString+HTML.m ├── Reachability │ ├── Reachability.h │ └── Reachability.m ├── UIViewController+CoreData.h ├── UIViewController+CoreData.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── ADBFeedReaderTest ├── ADBFeedReader-Info.plist ├── ADBFeedReader-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── FauxPasConfig └── main.fauxpas.json ├── LICENSE.markdown ├── Podfile ├── Podfile.lock ├── README.md └── analysis.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | profile 13 | *.moved-aside 14 | .DS_Store 15 | .idea/ 16 | DerivedData 17 | build/ 18 | *.swp 19 | *.lock 20 | Pods/ 21 | -------------------------------------------------------------------------------- /ADBFeedReader.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4B7AB8BD151D932E00FB21FA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8BC151D932E00FB21FA /* UIKit.framework */; }; 11 | 4B7AB8BF151D932E00FB21FA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8BE151D932E00FB21FA /* Foundation.framework */; }; 12 | 4B7AB8C1151D932E00FB21FA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8C0151D932E00FB21FA /* CoreGraphics.framework */; }; 13 | 4B7AB8C3151D932E00FB21FA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8C2151D932E00FB21FA /* CoreData.framework */; }; 14 | D30FD1551872083500711849 /* ADBFeedParser.m in Sources */ = {isa = PBXBuildFile; fileRef = D30FD1541872083500711849 /* ADBFeedParser.m */; }; 15 | D30FD15918720F4900711849 /* ADBMasterTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D30FD15818720F4900711849 /* ADBMasterTableViewController.xib */; }; 16 | D30FD15B1872129D00711849 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D30FD15A1872129D00711849 /* Images.xcassets */; }; 17 | D30FD15D187212E700711849 /* ADBDetailTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D30FD15C187212E700711849 /* ADBDetailTableViewController.xib */; }; 18 | D30FD15E1872171900711849 /* ADBFeedParser.m in Sources */ = {isa = PBXBuildFile; fileRef = D30FD1541872083500711849 /* ADBFeedParser.m */; }; 19 | D30FD15F1872181900711849 /* FeedReader.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D3E736A518356D4B006F0F5D /* FeedReader.xcdatamodeld */; }; 20 | D30FD16118721A1100711849 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D30FD16018721A1100711849 /* libPods.a */; }; 21 | D3E7369F18356D3D006F0F5D /* ADBImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E7369E18356D3D006F0F5D /* ADBImageView.m */; }; 22 | D3E736A618356D4B006F0F5D /* FeedInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736A218356D4B006F0F5D /* FeedInfo.m */; }; 23 | D3E736A718356D4B006F0F5D /* FeedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736A418356D4B006F0F5D /* FeedItem.m */; }; 24 | D3E736AE18356D64006F0F5D /* ADBDetailTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736AB18356D64006F0F5D /* ADBDetailTableViewController.m */; }; 25 | D3E736AF18356D64006F0F5D /* ADBMasterTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736AD18356D64006F0F5D /* ADBMasterTableViewController.m */; }; 26 | D3E736C018356D8B006F0F5D /* NSDate+InternetDateTime.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736BB18356D8B006F0F5D /* NSDate+InternetDateTime.m */; }; 27 | D3E736C118356D8B006F0F5D /* NSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736BD18356D8B006F0F5D /* NSString+HTML.m */; }; 28 | D3E736C218356D8B006F0F5D /* UIViewController+CoreData.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736BF18356D8B006F0F5D /* UIViewController+CoreData.m */; }; 29 | D3E736C818356DFD006F0F5D /* ADBWebBrowserViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = D3E736C418356DFD006F0F5D /* ADBWebBrowserViewController_iPad.xib */; }; 30 | D3E736C918356DFD006F0F5D /* ADBWebBrowserViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = D3E736C518356DFD006F0F5D /* ADBWebBrowserViewController_iPhone.xib */; }; 31 | D3E736CA18356DFD006F0F5D /* ADBWebBrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736C718356DFD006F0F5D /* ADBWebBrowserViewController.m */; }; 32 | D3E736D818356E14006F0F5D /* ADBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736D018356E14006F0F5D /* ADBAppDelegate.m */; }; 33 | D3E736DA18356E14006F0F5D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736D318356E14006F0F5D /* main.m */; }; 34 | D3E736DB18356E14006F0F5D /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = D3E736D618356E14006F0F5D /* Reachability.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 35 | D3E736E318356E40006F0F5D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D3E736E018356E40006F0F5D /* InfoPlist.strings */; }; 36 | D3E736EE18356EA7006F0F5D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D3E736EA18356EA7006F0F5D /* InfoPlist.strings */; }; 37 | F0104EA8170844FC00B30C6F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0104EA7170844FC00B30C6F /* SystemConfiguration.framework */; }; 38 | F01E416C18F172E100C14E9C /* ADBFeedInfoDTO.m in Sources */ = {isa = PBXBuildFile; fileRef = F01E416B18F172E100C14E9C /* ADBFeedInfoDTO.m */; }; 39 | F01E416F18F172EC00C14E9C /* ADBFeedItemDTO.m in Sources */ = {isa = PBXBuildFile; fileRef = F01E416E18F172EC00C14E9C /* ADBFeedItemDTO.m */; }; 40 | F01E417218F17AC200C14E9C /* FeedItem+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = F01E417118F17AC200C14E9C /* FeedItem+Additions.m */; }; 41 | F01E417518F17ACD00C14E9C /* FeedInfo+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = F01E417418F17ACD00C14E9C /* FeedInfo+Additions.m */; }; 42 | F047231117062ABD00EE78D2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8BC151D932E00FB21FA /* UIKit.framework */; }; 43 | F047231217062ABD00EE78D2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B7AB8BE151D932E00FB21FA /* Foundation.framework */; }; 44 | /* End PBXBuildFile section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = SOURCE_ROOT; }; 48 | 4B7AB8B8151D932E00FB21FA /* ADBFeedReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ADBFeedReader.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 4B7AB8BC151D932E00FB21FA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 50 | 4B7AB8BE151D932E00FB21FA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 51 | 4B7AB8C0151D932E00FB21FA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 52 | 4B7AB8C2151D932E00FB21FA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 53 | CB77CF92209341ACB8FD9942 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | D30FD1521872060D00711849 /* ADBFeedParserProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBFeedParserProtocol.h; path = ADBFeedReader/ADBFeedParserProtocol.h; sourceTree = SOURCE_ROOT; }; 55 | D30FD1531872083500711849 /* ADBFeedParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBFeedParser.h; path = ADBFeedReader/ADBFeedParser.h; sourceTree = SOURCE_ROOT; }; 56 | D30FD1541872083500711849 /* ADBFeedParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBFeedParser.m; path = ADBFeedReader/ADBFeedParser.m; sourceTree = SOURCE_ROOT; }; 57 | D30FD15818720F4900711849 /* ADBMasterTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ADBMasterTableViewController.xib; path = ADBFeedReader/ADBMasterTableViewController.xib; sourceTree = SOURCE_ROOT; }; 58 | D30FD15A1872129D00711849 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ADBFeedReader/Images.xcassets; sourceTree = SOURCE_ROOT; }; 59 | D30FD15C187212E700711849 /* ADBDetailTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ADBDetailTableViewController.xib; path = ADBFeedReader/ADBDetailTableViewController.xib; sourceTree = SOURCE_ROOT; }; 60 | D30FD16018721A1100711849 /* libPods.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libPods.a; path = "Pods/build/Debug-iphoneos/libPods.a"; sourceTree = ""; }; 61 | D3E7369D18356D3D006F0F5D /* ADBImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBImageView.h; path = ADBFeedReader/ADBImageView.h; sourceTree = SOURCE_ROOT; }; 62 | D3E7369E18356D3D006F0F5D /* ADBImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBImageView.m; path = ADBFeedReader/ADBImageView.m; sourceTree = SOURCE_ROOT; }; 63 | D3E736A118356D4B006F0F5D /* FeedInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FeedInfo.h; path = ADBFeedReader/FeedInfo.h; sourceTree = SOURCE_ROOT; }; 64 | D3E736A218356D4B006F0F5D /* FeedInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FeedInfo.m; path = ADBFeedReader/FeedInfo.m; sourceTree = SOURCE_ROOT; }; 65 | D3E736A318356D4B006F0F5D /* FeedItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FeedItem.h; path = ADBFeedReader/FeedItem.h; sourceTree = SOURCE_ROOT; }; 66 | D3E736A418356D4B006F0F5D /* FeedItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FeedItem.m; path = ADBFeedReader/FeedItem.m; sourceTree = SOURCE_ROOT; }; 67 | D3E736AA18356D64006F0F5D /* ADBDetailTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBDetailTableViewController.h; path = ADBFeedReader/ADBDetailTableViewController.h; sourceTree = SOURCE_ROOT; }; 68 | D3E736AB18356D64006F0F5D /* ADBDetailTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBDetailTableViewController.m; path = ADBFeedReader/ADBDetailTableViewController.m; sourceTree = SOURCE_ROOT; }; 69 | D3E736AC18356D64006F0F5D /* ADBMasterTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBMasterTableViewController.h; path = ADBFeedReader/ADBMasterTableViewController.h; sourceTree = SOURCE_ROOT; }; 70 | D3E736AD18356D64006F0F5D /* ADBMasterTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBMasterTableViewController.m; path = ADBFeedReader/ADBMasterTableViewController.m; sourceTree = SOURCE_ROOT; }; 71 | D3E736BA18356D8B006F0F5D /* NSDate+InternetDateTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDate+InternetDateTime.h"; path = "ADBFeedReader/NSDate+InternetDateTime.h"; sourceTree = SOURCE_ROOT; }; 72 | D3E736BB18356D8B006F0F5D /* NSDate+InternetDateTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDate+InternetDateTime.m"; path = "ADBFeedReader/NSDate+InternetDateTime.m"; sourceTree = SOURCE_ROOT; }; 73 | D3E736BC18356D8B006F0F5D /* NSString+HTML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSString+HTML.h"; path = "ADBFeedReader/NSString+HTML.h"; sourceTree = SOURCE_ROOT; }; 74 | D3E736BD18356D8B006F0F5D /* NSString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+HTML.m"; path = "ADBFeedReader/NSString+HTML.m"; sourceTree = SOURCE_ROOT; }; 75 | D3E736BE18356D8B006F0F5D /* UIViewController+CoreData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+CoreData.h"; path = "ADBFeedReader/UIViewController+CoreData.h"; sourceTree = SOURCE_ROOT; }; 76 | D3E736BF18356D8B006F0F5D /* UIViewController+CoreData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+CoreData.m"; path = "ADBFeedReader/UIViewController+CoreData.m"; sourceTree = SOURCE_ROOT; }; 77 | D3E736C418356DFD006F0F5D /* ADBWebBrowserViewController_iPad.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ADBWebBrowserViewController_iPad.xib; path = ADBFeedReader/ADBWebBrowserViewController_iPad.xib; sourceTree = SOURCE_ROOT; }; 78 | D3E736C518356DFD006F0F5D /* ADBWebBrowserViewController_iPhone.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ADBWebBrowserViewController_iPhone.xib; path = ADBFeedReader/ADBWebBrowserViewController_iPhone.xib; sourceTree = SOURCE_ROOT; }; 79 | D3E736C618356DFD006F0F5D /* ADBWebBrowserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBWebBrowserViewController.h; path = ADBFeedReader/ADBWebBrowserViewController.h; sourceTree = SOURCE_ROOT; }; 80 | D3E736C718356DFD006F0F5D /* ADBWebBrowserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBWebBrowserViewController.m; path = ADBFeedReader/ADBWebBrowserViewController.m; sourceTree = SOURCE_ROOT; }; 81 | D3E736CC18356E14006F0F5D /* ADBFeedReader-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "ADBFeedReader-Info.plist"; path = "ADBFeedReader/ADBFeedReader-Info.plist"; sourceTree = SOURCE_ROOT; }; 82 | D3E736CD18356E14006F0F5D /* ADBFeedReader-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ADBFeedReader-Prefix.pch"; path = "ADBFeedReader/ADBFeedReader-Prefix.pch"; sourceTree = SOURCE_ROOT; }; 83 | D3E736CE18356E14006F0F5D /* ADBMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBMacros.h; path = ADBFeedReader/ADBMacros.h; sourceTree = SOURCE_ROOT; }; 84 | D3E736CF18356E14006F0F5D /* ADBAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBAppDelegate.h; path = ADBFeedReader/ADBAppDelegate.h; sourceTree = SOURCE_ROOT; }; 85 | D3E736D018356E14006F0F5D /* ADBAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBAppDelegate.m; path = ADBFeedReader/ADBAppDelegate.m; sourceTree = SOURCE_ROOT; }; 86 | D3E736D118356E14006F0F5D /* ADBConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBConstants.h; path = ADBFeedReader/ADBConstants.h; sourceTree = SOURCE_ROOT; }; 87 | D3E736D318356E14006F0F5D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ADBFeedReader/main.m; sourceTree = SOURCE_ROOT; }; 88 | D3E736D518356E14006F0F5D /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 89 | D3E736D618356E14006F0F5D /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 90 | D3E736DE18356E40006F0F5D /* ADBFeedReader-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "ADBFeedReader-Info.plist"; sourceTree = ""; }; 91 | D3E736DF18356E40006F0F5D /* ADBFeedReader-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ADBFeedReader-Prefix.pch"; sourceTree = ""; }; 92 | D3E736E118356E40006F0F5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 93 | D3E736EB18356EA7006F0F5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = ADBFeedReader/en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; }; 94 | F0104EA7170844FC00B30C6F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 95 | F01E416A18F172E100C14E9C /* ADBFeedInfoDTO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBFeedInfoDTO.h; path = ADBFeedReader/ADBFeedInfoDTO.h; sourceTree = SOURCE_ROOT; }; 96 | F01E416B18F172E100C14E9C /* ADBFeedInfoDTO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBFeedInfoDTO.m; path = ADBFeedReader/ADBFeedInfoDTO.m; sourceTree = SOURCE_ROOT; }; 97 | F01E416D18F172EC00C14E9C /* ADBFeedItemDTO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ADBFeedItemDTO.h; path = ADBFeedReader/ADBFeedItemDTO.h; sourceTree = SOURCE_ROOT; }; 98 | F01E416E18F172EC00C14E9C /* ADBFeedItemDTO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ADBFeedItemDTO.m; path = ADBFeedReader/ADBFeedItemDTO.m; sourceTree = SOURCE_ROOT; }; 99 | F01E417018F17AC200C14E9C /* FeedItem+Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FeedItem+Additions.h"; path = "ADBFeedReader/FeedItem+Additions.h"; sourceTree = SOURCE_ROOT; }; 100 | F01E417118F17AC200C14E9C /* FeedItem+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FeedItem+Additions.m"; path = "ADBFeedReader/FeedItem+Additions.m"; sourceTree = SOURCE_ROOT; }; 101 | F01E417318F17ACD00C14E9C /* FeedInfo+Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FeedInfo+Additions.h"; path = "ADBFeedReader/FeedInfo+Additions.h"; sourceTree = SOURCE_ROOT; }; 102 | F01E417418F17ACD00C14E9C /* FeedInfo+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FeedInfo+Additions.m"; path = "ADBFeedReader/FeedInfo+Additions.m"; sourceTree = SOURCE_ROOT; }; 103 | F047230E17062ABD00EE78D2 /* ADBFeedReaderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ADBFeedReaderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | F092FD5218CBC7E700CB5264 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 105 | F092FD5418CBCA2C00CB5264 /* LICENSE.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.markdown; sourceTree = ""; }; 106 | F092FD5618CBD2B400CB5264 /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 107 | /* End PBXFileReference section */ 108 | 109 | /* Begin PBXFrameworksBuildPhase section */ 110 | 4B7AB8B5151D932E00FB21FA /* Frameworks */ = { 111 | isa = PBXFrameworksBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | D30FD16118721A1100711849 /* libPods.a in Frameworks */, 115 | F0104EA8170844FC00B30C6F /* SystemConfiguration.framework in Frameworks */, 116 | 4B7AB8BD151D932E00FB21FA /* UIKit.framework in Frameworks */, 117 | 4B7AB8BF151D932E00FB21FA /* Foundation.framework in Frameworks */, 118 | 4B7AB8C1151D932E00FB21FA /* CoreGraphics.framework in Frameworks */, 119 | 4B7AB8C3151D932E00FB21FA /* CoreData.framework in Frameworks */, 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | F047230A17062ABD00EE78D2 /* Frameworks */ = { 124 | isa = PBXFrameworksBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | F047231117062ABD00EE78D2 /* UIKit.framework in Frameworks */, 128 | F047231217062ABD00EE78D2 /* Foundation.framework in Frameworks */, 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXFrameworksBuildPhase section */ 133 | 134 | /* Begin PBXGroup section */ 135 | 4B7AB8AD151D932E00FB21FA = { 136 | isa = PBXGroup; 137 | children = ( 138 | F092FD5418CBCA2C00CB5264 /* LICENSE.markdown */, 139 | F092FD5218CBC7E700CB5264 /* README.md */, 140 | F092FD5618CBD2B400CB5264 /* Podfile */, 141 | 4B7AB8C4151D932E00FB21FA /* ADBFeedReader */, 142 | D3E736DD18356E40006F0F5D /* ADBFeedReaderTest */, 143 | 4B7AB8BB151D932E00FB21FA /* Frameworks */, 144 | 4B7AB8B9151D932E00FB21FA /* Products */, 145 | 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */, 146 | ); 147 | sourceTree = ""; 148 | }; 149 | 4B7AB8B9151D932E00FB21FA /* Products */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 4B7AB8B8151D932E00FB21FA /* ADBFeedReader.app */, 153 | F047230E17062ABD00EE78D2 /* ADBFeedReaderTests.xctest */, 154 | ); 155 | name = Products; 156 | sourceTree = ""; 157 | }; 158 | 4B7AB8BB151D932E00FB21FA /* Frameworks */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | D30FD16018721A1100711849 /* libPods.a */, 162 | 4B7AB8BC151D932E00FB21FA /* UIKit.framework */, 163 | 4B7AB8BE151D932E00FB21FA /* Foundation.framework */, 164 | 4B7AB8C0151D932E00FB21FA /* CoreGraphics.framework */, 165 | 4B7AB8C2151D932E00FB21FA /* CoreData.framework */, 166 | CB77CF92209341ACB8FD9942 /* libPods.a */, 167 | F0104EA7170844FC00B30C6F /* SystemConfiguration.framework */, 168 | ); 169 | name = Frameworks; 170 | sourceTree = ""; 171 | }; 172 | 4B7AB8C4151D932E00FB21FA /* ADBFeedReader */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | D3E736DC18356E2B006F0F5D /* Supporting Files */, 176 | D3E736D418356E14006F0F5D /* Reachability */, 177 | D3E736CB18356E03006F0F5D /* WebBrowser */, 178 | D3E736C318356D8E006F0F5D /* Categories */, 179 | D3E736B018356D68006F0F5D /* View Controllers */, 180 | D3E736A918356D4F006F0F5D /* Model */, 181 | D3E736A018356D40006F0F5D /* ADBImageView */, 182 | F004F0D11707555B000C5705 /* FeedParser */, 183 | F004F0CC17073FB1000C5705 /* Resources */, 184 | D30FD15A1872129D00711849 /* Images.xcassets */, 185 | ); 186 | name = ADBFeedReader; 187 | path = EFFeedReader; 188 | sourceTree = ""; 189 | }; 190 | D3E736A018356D40006F0F5D /* ADBImageView */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | D3E7369D18356D3D006F0F5D /* ADBImageView.h */, 194 | D3E7369E18356D3D006F0F5D /* ADBImageView.m */, 195 | ); 196 | name = ADBImageView; 197 | sourceTree = ""; 198 | }; 199 | D3E736A918356D4F006F0F5D /* Model */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | D3E736A118356D4B006F0F5D /* FeedInfo.h */, 203 | D3E736A218356D4B006F0F5D /* FeedInfo.m */, 204 | D3E736A318356D4B006F0F5D /* FeedItem.h */, 205 | D3E736A418356D4B006F0F5D /* FeedItem.m */, 206 | F01E417318F17ACD00C14E9C /* FeedInfo+Additions.h */, 207 | F01E417418F17ACD00C14E9C /* FeedInfo+Additions.m */, 208 | F01E417018F17AC200C14E9C /* FeedItem+Additions.h */, 209 | F01E417118F17AC200C14E9C /* FeedItem+Additions.m */, 210 | D3E736A518356D4B006F0F5D /* FeedReader.xcdatamodeld */, 211 | F01E416A18F172E100C14E9C /* ADBFeedInfoDTO.h */, 212 | F01E416B18F172E100C14E9C /* ADBFeedInfoDTO.m */, 213 | F01E416D18F172EC00C14E9C /* ADBFeedItemDTO.h */, 214 | F01E416E18F172EC00C14E9C /* ADBFeedItemDTO.m */, 215 | ); 216 | name = Model; 217 | sourceTree = ""; 218 | }; 219 | D3E736B018356D68006F0F5D /* View Controllers */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | D3E736AC18356D64006F0F5D /* ADBMasterTableViewController.h */, 223 | D3E736AD18356D64006F0F5D /* ADBMasterTableViewController.m */, 224 | D30FD15818720F4900711849 /* ADBMasterTableViewController.xib */, 225 | D3E736AA18356D64006F0F5D /* ADBDetailTableViewController.h */, 226 | D3E736AB18356D64006F0F5D /* ADBDetailTableViewController.m */, 227 | D30FD15C187212E700711849 /* ADBDetailTableViewController.xib */, 228 | ); 229 | name = "View Controllers"; 230 | sourceTree = ""; 231 | }; 232 | D3E736C318356D8E006F0F5D /* Categories */ = { 233 | isa = PBXGroup; 234 | children = ( 235 | D3E736BA18356D8B006F0F5D /* NSDate+InternetDateTime.h */, 236 | D3E736BB18356D8B006F0F5D /* NSDate+InternetDateTime.m */, 237 | D3E736BC18356D8B006F0F5D /* NSString+HTML.h */, 238 | D3E736BD18356D8B006F0F5D /* NSString+HTML.m */, 239 | D3E736BE18356D8B006F0F5D /* UIViewController+CoreData.h */, 240 | D3E736BF18356D8B006F0F5D /* UIViewController+CoreData.m */, 241 | ); 242 | name = Categories; 243 | sourceTree = ""; 244 | }; 245 | D3E736CB18356E03006F0F5D /* WebBrowser */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | D3E736C418356DFD006F0F5D /* ADBWebBrowserViewController_iPad.xib */, 249 | D3E736C518356DFD006F0F5D /* ADBWebBrowserViewController_iPhone.xib */, 250 | D3E736C618356DFD006F0F5D /* ADBWebBrowserViewController.h */, 251 | D3E736C718356DFD006F0F5D /* ADBWebBrowserViewController.m */, 252 | ); 253 | name = WebBrowser; 254 | sourceTree = ""; 255 | }; 256 | D3E736D418356E14006F0F5D /* Reachability */ = { 257 | isa = PBXGroup; 258 | children = ( 259 | D3E736D518356E14006F0F5D /* Reachability.h */, 260 | D3E736D618356E14006F0F5D /* Reachability.m */, 261 | ); 262 | name = Reachability; 263 | path = ADBFeedReader/Reachability; 264 | sourceTree = SOURCE_ROOT; 265 | }; 266 | D3E736DC18356E2B006F0F5D /* Supporting Files */ = { 267 | isa = PBXGroup; 268 | children = ( 269 | D3E736EA18356EA7006F0F5D /* InfoPlist.strings */, 270 | D3E736CC18356E14006F0F5D /* ADBFeedReader-Info.plist */, 271 | D3E736CD18356E14006F0F5D /* ADBFeedReader-Prefix.pch */, 272 | D3E736CE18356E14006F0F5D /* ADBMacros.h */, 273 | D3E736CF18356E14006F0F5D /* ADBAppDelegate.h */, 274 | D3E736D018356E14006F0F5D /* ADBAppDelegate.m */, 275 | D3E736D118356E14006F0F5D /* ADBConstants.h */, 276 | D3E736D318356E14006F0F5D /* main.m */, 277 | ); 278 | name = "Supporting Files"; 279 | sourceTree = ""; 280 | }; 281 | D3E736DD18356E40006F0F5D /* ADBFeedReaderTest */ = { 282 | isa = PBXGroup; 283 | children = ( 284 | D3E736DE18356E40006F0F5D /* ADBFeedReader-Info.plist */, 285 | D3E736DF18356E40006F0F5D /* ADBFeedReader-Prefix.pch */, 286 | D3E736E018356E40006F0F5D /* InfoPlist.strings */, 287 | ); 288 | path = ADBFeedReaderTest; 289 | sourceTree = ""; 290 | }; 291 | F004F0CC17073FB1000C5705 /* Resources */ = { 292 | isa = PBXGroup; 293 | children = ( 294 | ); 295 | name = Resources; 296 | path = ..; 297 | sourceTree = ""; 298 | }; 299 | F004F0D11707555B000C5705 /* FeedParser */ = { 300 | isa = PBXGroup; 301 | children = ( 302 | D30FD1531872083500711849 /* ADBFeedParser.h */, 303 | D30FD1541872083500711849 /* ADBFeedParser.m */, 304 | D30FD1521872060D00711849 /* ADBFeedParserProtocol.h */, 305 | ); 306 | name = FeedParser; 307 | sourceTree = ""; 308 | }; 309 | /* End PBXGroup section */ 310 | 311 | /* Begin PBXNativeTarget section */ 312 | 4B7AB8B7151D932E00FB21FA /* ADBFeedReader */ = { 313 | isa = PBXNativeTarget; 314 | buildConfigurationList = 4B7AB8DE151D932E00FB21FA /* Build configuration list for PBXNativeTarget "ADBFeedReader" */; 315 | buildPhases = ( 316 | 4B7AB8B4151D932E00FB21FA /* Sources */, 317 | 4B7AB8B5151D932E00FB21FA /* Frameworks */, 318 | 4B7AB8B6151D932E00FB21FA /* Resources */, 319 | 8911E994A2FE44458EA4661A /* Copy Pods Resources */, 320 | ); 321 | buildRules = ( 322 | ); 323 | dependencies = ( 324 | ); 325 | name = ADBFeedReader; 326 | productName = FailedBankCD; 327 | productReference = 4B7AB8B8151D932E00FB21FA /* ADBFeedReader.app */; 328 | productType = "com.apple.product-type.application"; 329 | }; 330 | F047230D17062ABD00EE78D2 /* ADBFeedReaderTests */ = { 331 | isa = PBXNativeTarget; 332 | buildConfigurationList = F047231D17062ABD00EE78D2 /* Build configuration list for PBXNativeTarget "ADBFeedReaderTests" */; 333 | buildPhases = ( 334 | F047230917062ABD00EE78D2 /* Sources */, 335 | F047230A17062ABD00EE78D2 /* Frameworks */, 336 | F047230B17062ABD00EE78D2 /* Resources */, 337 | F047230C17062ABD00EE78D2 /* ShellScript */, 338 | ); 339 | buildRules = ( 340 | ); 341 | dependencies = ( 342 | ); 343 | name = ADBFeedReaderTests; 344 | productName = EFFeedReader; 345 | productReference = F047230E17062ABD00EE78D2 /* ADBFeedReaderTests.xctest */; 346 | productType = "com.apple.product-type.bundle.unit-test"; 347 | }; 348 | /* End PBXNativeTarget section */ 349 | 350 | /* Begin PBXProject section */ 351 | 4B7AB8AF151D932E00FB21FA /* Project object */ = { 352 | isa = PBXProject; 353 | attributes = { 354 | CLASSPREFIX = FBCD; 355 | LastTestingUpgradeCheck = 0510; 356 | LastUpgradeCheck = 0510; 357 | ORGANIZATIONNAME = "Adam Burkepile"; 358 | }; 359 | buildConfigurationList = 4B7AB8B2151D932E00FB21FA /* Build configuration list for PBXProject "ADBFeedReader" */; 360 | compatibilityVersion = "Xcode 3.2"; 361 | developmentRegion = English; 362 | hasScannedForEncodings = 0; 363 | knownRegions = ( 364 | en, 365 | ); 366 | mainGroup = 4B7AB8AD151D932E00FB21FA; 367 | productRefGroup = 4B7AB8B9151D932E00FB21FA /* Products */; 368 | projectDirPath = ""; 369 | projectRoot = ""; 370 | targets = ( 371 | 4B7AB8B7151D932E00FB21FA /* ADBFeedReader */, 372 | F047230D17062ABD00EE78D2 /* ADBFeedReaderTests */, 373 | ); 374 | }; 375 | /* End PBXProject section */ 376 | 377 | /* Begin PBXResourcesBuildPhase section */ 378 | 4B7AB8B6151D932E00FB21FA /* Resources */ = { 379 | isa = PBXResourcesBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | D3E736C818356DFD006F0F5D /* ADBWebBrowserViewController_iPad.xib in Resources */, 383 | D3E736EE18356EA7006F0F5D /* InfoPlist.strings in Resources */, 384 | D30FD15B1872129D00711849 /* Images.xcassets in Resources */, 385 | D30FD15918720F4900711849 /* ADBMasterTableViewController.xib in Resources */, 386 | D30FD15D187212E700711849 /* ADBDetailTableViewController.xib in Resources */, 387 | D3E736E318356E40006F0F5D /* InfoPlist.strings in Resources */, 388 | D3E736C918356DFD006F0F5D /* ADBWebBrowserViewController_iPhone.xib in Resources */, 389 | ); 390 | runOnlyForDeploymentPostprocessing = 0; 391 | }; 392 | F047230B17062ABD00EE78D2 /* Resources */ = { 393 | isa = PBXResourcesBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | ); 397 | runOnlyForDeploymentPostprocessing = 0; 398 | }; 399 | /* End PBXResourcesBuildPhase section */ 400 | 401 | /* Begin PBXShellScriptBuildPhase section */ 402 | 8911E994A2FE44458EA4661A /* Copy Pods Resources */ = { 403 | isa = PBXShellScriptBuildPhase; 404 | buildActionMask = 2147483647; 405 | files = ( 406 | ); 407 | inputPaths = ( 408 | ); 409 | name = "Copy Pods Resources"; 410 | outputPaths = ( 411 | ); 412 | runOnlyForDeploymentPostprocessing = 0; 413 | shellPath = /bin/sh; 414 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 415 | }; 416 | F047230C17062ABD00EE78D2 /* ShellScript */ = { 417 | isa = PBXShellScriptBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | ); 421 | inputPaths = ( 422 | ); 423 | outputPaths = ( 424 | ); 425 | runOnlyForDeploymentPostprocessing = 0; 426 | shellPath = /bin/sh; 427 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 428 | }; 429 | /* End PBXShellScriptBuildPhase section */ 430 | 431 | /* Begin PBXSourcesBuildPhase section */ 432 | 4B7AB8B4151D932E00FB21FA /* Sources */ = { 433 | isa = PBXSourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | D30FD15F1872181900711849 /* FeedReader.xcdatamodeld in Sources */, 437 | D3E736AF18356D64006F0F5D /* ADBMasterTableViewController.m in Sources */, 438 | F01E416F18F172EC00C14E9C /* ADBFeedItemDTO.m in Sources */, 439 | F01E417218F17AC200C14E9C /* FeedItem+Additions.m in Sources */, 440 | D3E736CA18356DFD006F0F5D /* ADBWebBrowserViewController.m in Sources */, 441 | D30FD1551872083500711849 /* ADBFeedParser.m in Sources */, 442 | D3E736DA18356E14006F0F5D /* main.m in Sources */, 443 | D3E736D818356E14006F0F5D /* ADBAppDelegate.m in Sources */, 444 | D3E736C018356D8B006F0F5D /* NSDate+InternetDateTime.m in Sources */, 445 | D3E736C118356D8B006F0F5D /* NSString+HTML.m in Sources */, 446 | D3E7369F18356D3D006F0F5D /* ADBImageView.m in Sources */, 447 | D3E736C218356D8B006F0F5D /* UIViewController+CoreData.m in Sources */, 448 | D3E736AE18356D64006F0F5D /* ADBDetailTableViewController.m in Sources */, 449 | D3E736A618356D4B006F0F5D /* FeedInfo.m in Sources */, 450 | F01E416C18F172E100C14E9C /* ADBFeedInfoDTO.m in Sources */, 451 | F01E417518F17ACD00C14E9C /* FeedInfo+Additions.m in Sources */, 452 | D3E736A718356D4B006F0F5D /* FeedItem.m in Sources */, 453 | D3E736DB18356E14006F0F5D /* Reachability.m in Sources */, 454 | ); 455 | runOnlyForDeploymentPostprocessing = 0; 456 | }; 457 | F047230917062ABD00EE78D2 /* Sources */ = { 458 | isa = PBXSourcesBuildPhase; 459 | buildActionMask = 2147483647; 460 | files = ( 461 | D30FD15E1872171900711849 /* ADBFeedParser.m in Sources */, 462 | ); 463 | runOnlyForDeploymentPostprocessing = 0; 464 | }; 465 | /* End PBXSourcesBuildPhase section */ 466 | 467 | /* Begin PBXVariantGroup section */ 468 | D3E736E018356E40006F0F5D /* InfoPlist.strings */ = { 469 | isa = PBXVariantGroup; 470 | children = ( 471 | D3E736E118356E40006F0F5D /* en */, 472 | ); 473 | name = InfoPlist.strings; 474 | sourceTree = ""; 475 | }; 476 | D3E736EA18356EA7006F0F5D /* InfoPlist.strings */ = { 477 | isa = PBXVariantGroup; 478 | children = ( 479 | D3E736EB18356EA7006F0F5D /* en */, 480 | ); 481 | name = InfoPlist.strings; 482 | sourceTree = ""; 483 | }; 484 | /* End PBXVariantGroup section */ 485 | 486 | /* Begin XCBuildConfiguration section */ 487 | 4B7AB8DC151D932E00FB21FA /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ALWAYS_SEARCH_USER_PATHS = NO; 491 | CLANG_ENABLE_OBJC_ARC = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_CONSTANT_CONVERSION = YES; 494 | CLANG_WARN_EMPTY_BODY = YES; 495 | CLANG_WARN_ENUM_CONVERSION = YES; 496 | CLANG_WARN_INT_CONVERSION = YES; 497 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 498 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 499 | COPY_PHASE_STRIP = NO; 500 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 501 | GCC_C_LANGUAGE_STANDARD = gnu99; 502 | GCC_DYNAMIC_NO_PIC = NO; 503 | GCC_OPTIMIZATION_LEVEL = 0; 504 | GCC_PREPROCESSOR_DEFINITIONS = ( 505 | "DEBUG=1", 506 | "$(inherited)", 507 | ); 508 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 509 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 510 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 511 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 512 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 513 | GCC_WARN_UNDECLARED_SELECTOR = YES; 514 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 515 | GCC_WARN_UNUSED_FUNCTION = YES; 516 | GCC_WARN_UNUSED_VARIABLE = YES; 517 | ONLY_ACTIVE_ARCH = YES; 518 | OTHER_LDFLAGS = ( 519 | "-ObjC", 520 | "-all_load", 521 | ); 522 | SDKROOT = iphoneos; 523 | VALID_ARCHS = "armv7 armv7s"; 524 | }; 525 | name = Debug; 526 | }; 527 | 4B7AB8DD151D932E00FB21FA /* Release */ = { 528 | isa = XCBuildConfiguration; 529 | buildSettings = { 530 | ALWAYS_SEARCH_USER_PATHS = NO; 531 | CLANG_ENABLE_OBJC_ARC = YES; 532 | CLANG_WARN_BOOL_CONVERSION = YES; 533 | CLANG_WARN_CONSTANT_CONVERSION = YES; 534 | CLANG_WARN_EMPTY_BODY = YES; 535 | CLANG_WARN_ENUM_CONVERSION = YES; 536 | CLANG_WARN_INT_CONVERSION = YES; 537 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 538 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 539 | COPY_PHASE_STRIP = YES; 540 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 541 | GCC_C_LANGUAGE_STANDARD = gnu99; 542 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 543 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 544 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 545 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 546 | GCC_WARN_UNDECLARED_SELECTOR = YES; 547 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 548 | GCC_WARN_UNUSED_FUNCTION = YES; 549 | GCC_WARN_UNUSED_VARIABLE = YES; 550 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 551 | OTHER_LDFLAGS = ( 552 | "-ObjC", 553 | "-all_load", 554 | ); 555 | SDKROOT = iphoneos; 556 | VALIDATE_PRODUCT = YES; 557 | VALID_ARCHS = "armv7 armv7s"; 558 | }; 559 | name = Release; 560 | }; 561 | 4B7AB8DF151D932E00FB21FA /* Debug */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 567 | FRAMEWORK_SEARCH_PATHS = ( 568 | "$(inherited)", 569 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 570 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 571 | ); 572 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 573 | GCC_PREFIX_HEADER = "ADBFeedReader/ADBFeedReader-Prefix.pch"; 574 | GCC_PREPROCESSOR_DEFINITIONS = ( 575 | "DEBUG=1", 576 | "$(inherited)", 577 | ); 578 | GCC_TREAT_WARNINGS_AS_ERRORS = NO; 579 | INFOPLIST_FILE = "ADBFeedReader/ADBFeedReader-Info.plist"; 580 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 581 | PRODUCT_NAME = ADBFeedReader; 582 | VALID_ARCHS = "armv7 armv7s"; 583 | WRAPPER_EXTENSION = app; 584 | }; 585 | name = Debug; 586 | }; 587 | 4B7AB8E0151D932E00FB21FA /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | baseConfigurationReference = 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */; 590 | buildSettings = { 591 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 592 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 593 | FRAMEWORK_SEARCH_PATHS = ( 594 | "$(inherited)", 595 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 596 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 597 | ); 598 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 599 | GCC_PREFIX_HEADER = "ADBFeedReader/ADBFeedReader-Prefix.pch"; 600 | GCC_TREAT_WARNINGS_AS_ERRORS = NO; 601 | INFOPLIST_FILE = "ADBFeedReader/ADBFeedReader-Info.plist"; 602 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 603 | PRODUCT_NAME = ADBFeedReader; 604 | VALID_ARCHS = "armv7 armv7s"; 605 | WRAPPER_EXTENSION = app; 606 | }; 607 | name = Release; 608 | }; 609 | F047231E17062ABD00EE78D2 /* Debug */ = { 610 | isa = XCBuildConfiguration; 611 | baseConfigurationReference = 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */; 612 | buildSettings = { 613 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 614 | CLANG_CXX_LIBRARY = "libc++"; 615 | CLANG_WARN_CONSTANT_CONVERSION = YES; 616 | CLANG_WARN_EMPTY_BODY = YES; 617 | CLANG_WARN_ENUM_CONVERSION = YES; 618 | CLANG_WARN_INT_CONVERSION = YES; 619 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 620 | FRAMEWORK_SEARCH_PATHS = ( 621 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 622 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 623 | "$(inherited)", 624 | ); 625 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 626 | GCC_PREFIX_HEADER = "ADBFeedReader/ADBFeedReader-Prefix.pch"; 627 | INFOPLIST_FILE = "ADBFeedReader/ADBFeedReader-Info.plist"; 628 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 629 | ONLY_ACTIVE_ARCH = YES; 630 | PRODUCT_NAME = ADBFeedReaderTests; 631 | VALID_ARCHS = "armv7 armv7s"; 632 | }; 633 | name = Debug; 634 | }; 635 | F047231F17062ABD00EE78D2 /* Release */ = { 636 | isa = XCBuildConfiguration; 637 | baseConfigurationReference = 06880BB34C7D43CF958E34A1 /* Pods.xcconfig */; 638 | buildSettings = { 639 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 640 | CLANG_CXX_LIBRARY = "libc++"; 641 | CLANG_WARN_CONSTANT_CONVERSION = YES; 642 | CLANG_WARN_EMPTY_BODY = YES; 643 | CLANG_WARN_ENUM_CONVERSION = YES; 644 | CLANG_WARN_INT_CONVERSION = YES; 645 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 646 | FRAMEWORK_SEARCH_PATHS = ( 647 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 648 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 649 | "$(inherited)", 650 | ); 651 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 652 | GCC_PREFIX_HEADER = "ADBFeedReader/ADBFeedReader-Prefix.pch"; 653 | INFOPLIST_FILE = "ADBFeedReader/ADBFeedReader-Info.plist"; 654 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 655 | PRODUCT_NAME = ADBFeedReaderTests; 656 | VALID_ARCHS = "armv7 armv7s"; 657 | }; 658 | name = Release; 659 | }; 660 | /* End XCBuildConfiguration section */ 661 | 662 | /* Begin XCConfigurationList section */ 663 | 4B7AB8B2151D932E00FB21FA /* Build configuration list for PBXProject "ADBFeedReader" */ = { 664 | isa = XCConfigurationList; 665 | buildConfigurations = ( 666 | 4B7AB8DC151D932E00FB21FA /* Debug */, 667 | 4B7AB8DD151D932E00FB21FA /* Release */, 668 | ); 669 | defaultConfigurationIsVisible = 0; 670 | defaultConfigurationName = Release; 671 | }; 672 | 4B7AB8DE151D932E00FB21FA /* Build configuration list for PBXNativeTarget "ADBFeedReader" */ = { 673 | isa = XCConfigurationList; 674 | buildConfigurations = ( 675 | 4B7AB8DF151D932E00FB21FA /* Debug */, 676 | 4B7AB8E0151D932E00FB21FA /* Release */, 677 | ); 678 | defaultConfigurationIsVisible = 0; 679 | defaultConfigurationName = Release; 680 | }; 681 | F047231D17062ABD00EE78D2 /* Build configuration list for PBXNativeTarget "ADBFeedReaderTests" */ = { 682 | isa = XCConfigurationList; 683 | buildConfigurations = ( 684 | F047231E17062ABD00EE78D2 /* Debug */, 685 | F047231F17062ABD00EE78D2 /* Release */, 686 | ); 687 | defaultConfigurationIsVisible = 0; 688 | defaultConfigurationName = Release; 689 | }; 690 | /* End XCConfigurationList section */ 691 | 692 | /* Begin XCVersionGroup section */ 693 | D3E736A518356D4B006F0F5D /* FeedReader.xcdatamodeld */ = { 694 | isa = XCVersionGroup; 695 | children = ( 696 | ); 697 | name = FeedReader.xcdatamodeld; 698 | path = ../ADBFeedReader/FeedReader.xcdatamodeld; 699 | sourceTree = ""; 700 | versionGroupType = wrapper.xcdatamodel; 701 | }; 702 | /* End XCVersionGroup section */ 703 | }; 704 | rootObject = 4B7AB8AF151D932E00FB21FA /* Project object */; 705 | } 706 | -------------------------------------------------------------------------------- /ADBFeedReader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ADBFeedReader.xcodeproj/xcshareddata/xcschemes/ADBFeedReader.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /ADBFeedReader.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ADBFeedReader.xcworkspace/xcshareddata/ADBFeedReader.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 51A7CD21-B2BF-426F-82B5-0B797B48B91D 9 | IDESourceControlProjectName 10 | ADBFeedReader 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 4238241F-A638-4240-BD9C-1876B97DA0BB 14 | https://github.com/albertodebortoli/ADBFeedReader.git 15 | 16 | IDESourceControlProjectPath 17 | ADBFeedReader.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 4238241F-A638-4240-BD9C-1876B97DA0BB 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/albertodebortoli/ADBFeedReader.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 4238241F-A638-4240-BD9C-1876B97DA0BB 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 4238241F-A638-4240-BD9C-1876B97DA0BB 36 | IDESourceControlWCCName 37 | ADBFeedReader-original 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBAppDelegate.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ADBAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 16 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 17 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 18 | 19 | - (void)saveContext; 20 | - (NSURL *)applicationDocumentsDirectory; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBAppDelegate.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBAppDelegate.h" 10 | #import "ADBMasterTableViewController.h" 11 | #import "Reachability.h" 12 | #import "ADBConstants.h" 13 | #import "ADBFeedParser.h" 14 | 15 | @implementation ADBAppDelegate 16 | 17 | @synthesize window = _window; 18 | @synthesize managedObjectContext = __managedObjectContext; 19 | @synthesize managedObjectModel = __managedObjectModel; 20 | @synthesize persistentStoreCoordinator = __persistentStoreCoordinator; 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 23 | { 24 | NSURL *feedURL = [NSURL URLWithString:kADBFeedURL]; 25 | 26 | // create the parser 27 | ADBFeedParser *feedParser = [[ADBFeedParser alloc] initWithURL:feedURL]; 28 | 29 | ADBMasterTableViewController *masterTableViewController = [[ADBMasterTableViewController alloc] initWithFeedParser:feedParser]; 30 | 31 | feedParser.delegate = masterTableViewController; 32 | 33 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:masterTableViewController]; 34 | masterTableViewController.managedObjectContext = self.managedObjectContext; 35 | 36 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 37 | self.window.rootViewController = navigationController; 38 | 39 | [self.window makeKeyAndVisible]; 40 | 41 | return YES; 42 | } 43 | 44 | - (void)applicationWillResignActive:(UIApplication *)application 45 | { 46 | // 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. 47 | // 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. 48 | } 49 | 50 | - (void)applicationDidEnterBackground:(UIApplication *)application 51 | { 52 | // 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. 53 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 54 | } 55 | 56 | - (void)applicationWillEnterForeground:(UIApplication *)application 57 | { 58 | // 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. 59 | } 60 | 61 | - (void)applicationDidBecomeActive:(UIApplication *)application 62 | { 63 | // 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. 64 | } 65 | 66 | - (void)applicationWillTerminate:(UIApplication *)application 67 | { 68 | // Saves changes in the application's managed object context before the application terminates. 69 | [self saveContext]; 70 | } 71 | 72 | - (void)saveContext 73 | { 74 | NSError *error = nil; 75 | NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 76 | if (managedObjectContext != nil) { 77 | if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { 78 | // Replace this implementation with code to handle the error appropriately. 79 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 80 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 81 | abort(); 82 | } 83 | } 84 | } 85 | 86 | #pragma mark - Core Data stack 87 | 88 | // Returns the managed object context for the application. 89 | // If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application. 90 | - (NSManagedObjectContext *)managedObjectContext 91 | { 92 | if (__managedObjectContext != nil) { 93 | return __managedObjectContext; 94 | } 95 | 96 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 97 | if (coordinator != nil) { 98 | __managedObjectContext = [[NSManagedObjectContext alloc] init]; 99 | [__managedObjectContext setPersistentStoreCoordinator:coordinator]; 100 | } 101 | return __managedObjectContext; 102 | } 103 | 104 | // Returns the managed object model for the application. 105 | // If the model doesn't already exist, it is created from the application's model. 106 | - (NSManagedObjectModel *)managedObjectModel 107 | { 108 | if (__managedObjectModel != nil) { 109 | return __managedObjectModel; 110 | } 111 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"FeedReader" withExtension:@"mom" subdirectory:@"FeedReader.momd"]; 112 | __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 113 | 114 | if (!__managedObjectModel) { 115 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"FeedReader" withExtension:@"momd"]; 116 | __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 117 | } 118 | 119 | if (!__managedObjectModel) { 120 | abort(); 121 | } 122 | 123 | return __managedObjectModel; 124 | } 125 | 126 | // Returns the persistent store coordinator for the application. 127 | // If the coordinator doesn't already exist, it is created and the application's store added to it. 128 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 129 | { 130 | if (__persistentStoreCoordinator != nil) { 131 | return __persistentStoreCoordinator; 132 | } 133 | 134 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"FeedReader.sqlite"]; 135 | 136 | NSError *error = nil; 137 | __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 138 | if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 139 | 140 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 141 | 142 | // Tip: if I get here, maybe the model has changed 143 | abort(); 144 | } 145 | 146 | return __persistentStoreCoordinator; 147 | } 148 | 149 | #pragma mark - Application's Documents directory 150 | 151 | // Returns the URL to the application's Documents directory. 152 | - (NSURL *)applicationDocumentsDirectory 153 | { 154 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 155 | } 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBConstants.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #pragma once 10 | 11 | static NSString __attribute__((unused)) *kADBFeedURL = @"http://feeds.bbci.co.uk/news/rss.xml"; 12 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBDetailTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBDetailTableViewController.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ADBFeedItemDTO.h" 11 | #import "ADBWebBrowserViewController.h" 12 | #import "ADBImageView.h" 13 | 14 | @interface ADBDetailTableViewController : UITableViewController 15 | 16 | @property (nonatomic, strong) ADBFeedItemDTO *item; 17 | @property (nonatomic, copy) NSString *dateString; 18 | @property (nonatomic, copy) NSString *summaryString; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBDetailTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBDetailTableViewController.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBDetailTableViewController.h" 10 | #import "NSString+HTML.h" 11 | 12 | typedef NS_ENUM(NSInteger, Sections) { 13 | SectionHeader = 0, 14 | SectionDetail = 1 15 | }; 16 | 17 | typedef NS_ENUM(NSInteger, HeaderRows) { 18 | SectionHeaderTitle = 0, 19 | SectionHeaderDate = 1, 20 | SectionHeaderURL = 2, 21 | SectionHeaderImage = 3 22 | }; 23 | 24 | typedef NS_ENUM(NSInteger, DetailRows) { 25 | SectionDetailSummary = 0, 26 | SectionDetailImage = 1 27 | }; 28 | 29 | @interface ADBDetailTableViewController () 30 | 33 | 34 | @end 35 | 36 | @implementation ADBDetailTableViewController 37 | 38 | #pragma mark - View lifecycle 39 | 40 | - (void)viewDidLoad 41 | { 42 | [super viewDidLoad]; 43 | 44 | // Date 45 | if (self.item.date) { 46 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 47 | [formatter setDateStyle:NSDateFormatterMediumStyle]; 48 | [formatter setTimeStyle:NSDateFormatterMediumStyle]; 49 | self.dateString = [formatter stringFromDate:self.item.date]; 50 | } 51 | 52 | // Summary 53 | if (self.item.summary) { 54 | self.summaryString = self.item.summary; 55 | } else { 56 | self.summaryString = @""; 57 | } 58 | 59 | self.title = self.item.title; 60 | 61 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction 62 | target:self 63 | action:@selector(openURLPressed:)]; 64 | } 65 | 66 | #pragma mark - TableViewDataSource 67 | 68 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 69 | { 70 | return 2; 71 | } 72 | 73 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 74 | { 75 | switch (section) { 76 | case 0: 77 | return 3; 78 | case 1: 79 | return self.item.image_url ? 2 : 1; 80 | default: 81 | return 0; 82 | } 83 | } 84 | 85 | // Customize the appearance of table view cells. 86 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 87 | { 88 | // Get cell 89 | static NSString *CellIdentifier = @"CellA"; 90 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 91 | if (cell == nil) { 92 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 93 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 94 | } 95 | 96 | // Display 97 | cell.textLabel.textColor = [UIColor blackColor]; 98 | cell.textLabel.font = [UIFont systemFontOfSize:15]; 99 | if (self.item) { 100 | // Item Info 101 | NSString *itemTitle = self.item.title ?: @""; 102 | 103 | // Display 104 | switch (indexPath.section) { 105 | case SectionHeader: { 106 | // Header 107 | switch (indexPath.row) { 108 | case SectionHeaderTitle: 109 | cell.textLabel.font = [UIFont boldSystemFontOfSize:15]; 110 | cell.textLabel.text = itemTitle; 111 | break; 112 | case SectionHeaderDate: 113 | cell.textLabel.text = self.dateString ?: @""; 114 | break; 115 | case SectionHeaderURL: 116 | cell.textLabel.text = self.item.link ?: @""; 117 | cell.textLabel.textColor = [UIColor blueColor]; 118 | cell.selectionStyle = UITableViewCellSelectionStyleBlue; 119 | break; 120 | } 121 | break; 122 | } 123 | case SectionDetail: { 124 | switch (indexPath.row) { 125 | case SectionDetailSummary: 126 | cell.textLabel.text = self.summaryString; 127 | cell.textLabel.numberOfLines = 0; 128 | break; 129 | case SectionDetailImage: { 130 | ADBImageView *imageView = [[ADBImageView alloc] initWithFrame:CGRectMake(10, 10, 280, 180)]; 131 | imageView.contentMode = UIViewContentModeScaleAspectFill; 132 | imageView.delegate = self; 133 | imageView.caching = NO; 134 | 135 | // placeholder 136 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"icon" ofType:@"png"]; 137 | UIImage *image = [UIImage imageWithContentsOfFile:filePath]; 138 | 139 | // item media URL 140 | NSURL *imageURL = [NSURL URLWithString:self.item.image_url]; 141 | [imageView setImageWithURL:imageURL placeholderImage:image]; 142 | 143 | [cell.contentView addSubview:imageView]; 144 | } 145 | } 146 | } 147 | default: 148 | break; 149 | } 150 | } 151 | 152 | return cell; 153 | } 154 | 155 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 156 | { 157 | if (indexPath.section == SectionHeader) { 158 | // Regular 159 | return 34; 160 | 161 | } else { 162 | if (indexPath.row == SectionDetailSummary) { 163 | // Get height of summary 164 | CGSize size = [self.summaryString sizeWithFont:[UIFont systemFontOfSize:15] 165 | constrainedToSize:CGSizeMake(self.view.bounds.size.width - 40, MAXFLOAT) 166 | lineBreakMode:NSLineBreakByWordWrapping]; 167 | return size.height + 16; // Add padding 168 | } else { 169 | return 200.0f; 170 | } 171 | } 172 | } 173 | 174 | #pragma mark - TableViewDelegate 175 | 176 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 177 | { 178 | if (!self.item.link.length) { 179 | return; 180 | } 181 | 182 | // Link 183 | if (indexPath.section == SectionHeader && indexPath.row == SectionHeaderURL) { 184 | [self _openActionSheet]; 185 | } 186 | 187 | // Deselect 188 | [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; 189 | 190 | } 191 | 192 | #pragma mark - UIActionSheetDelegate 193 | 194 | - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex 195 | { 196 | switch (buttonIndex) { 197 | case 0: 198 | [self _openInSafari]; 199 | break; 200 | case 1: { 201 | NSURL *url = [NSURL URLWithString:self.item.link]; 202 | ADBWebBrowserViewController *webBrowserController = [[ADBWebBrowserViewController alloc] initWithURL:url delegate:self]; 203 | [self presentViewController:webBrowserController animated:YES completion:nil]; 204 | break; 205 | } 206 | default: 207 | break; 208 | } 209 | } 210 | 211 | #pragma mark - ADBImageViewDelegate 212 | 213 | - (void)adbImageView:(ADBImageView *)view willUpdateImage:(UIImage *)image 214 | { 215 | view.alpha = 0.0; 216 | [UIView animateWithDuration:0.7 animations:^{ view.alpha = 1.0; }]; 217 | } 218 | 219 | - (void)adbImageView:(ADBImageView *)view failedLoadingWithError:(NSError *)error 220 | { 221 | NSLog(@"Image failed loading: %@", [error localizedDescription]); 222 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 223 | message:@"Error Loading Image URL" 224 | delegate:nil 225 | cancelButtonTitle:nil 226 | otherButtonTitles:@"Ok", nil]; 227 | [alert show]; 228 | } 229 | 230 | #pragma mark - Actions 231 | 232 | - (void)openURLPressed:(id)sender 233 | { 234 | [self _openInSafari]; 235 | } 236 | 237 | #pragma mark - Private 238 | 239 | - (void)_openActionSheet 240 | { 241 | UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Open URL", nil) 242 | delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:nil otherButtonTitles:@"Open in Safari", @"Open in-app", nil]; 243 | [actionSheet showInView:self.view]; 244 | } 245 | 246 | - (void)_openInSafari 247 | { 248 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.item.link]]; 249 | } 250 | 251 | @end 252 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBDetailTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedInfoDTO.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedInfoDTO.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ADBFeedInfoDTO : NSObject 12 | 13 | @property (nonatomic, copy) NSString * title; 14 | @property (nonatomic, copy) NSString * author; 15 | @property (nonatomic, copy) NSString * link; 16 | @property (nonatomic, copy) NSString * summary; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedInfoDTO.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedInfoDTO.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "ADBFeedInfoDTO.h" 10 | 11 | @implementation ADBFeedInfoDTO 12 | 13 | - (NSString *)description 14 | { 15 | return [NSString stringWithFormat:@"<%@: %p> title: %@", [self class], self, self.title]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedItemDTO.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedItemDTO.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ADBFeedItemDTO : NSObject 12 | 13 | @property (nonatomic, copy) NSString * title; 14 | @property (nonatomic, copy) NSString * summary; 15 | @property (nonatomic, copy) NSString * link; 16 | @property (nonatomic, copy) NSString * content; 17 | @property (nonatomic, copy) NSString * identifier; 18 | @property (nonatomic, copy) NSString * image_url; 19 | @property (nonatomic, strong) NSDate * update_date; 20 | @property (nonatomic, strong) NSDate * date; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedItemDTO.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedItemDTO.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "ADBFeedItemDTO.h" 10 | 11 | @implementation ADBFeedItemDTO 12 | 13 | - (NSString *)description 14 | { 15 | return [NSString stringWithFormat:@"<%@: %p> title: %@", [self class], self, self.title]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBXMLParser.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ADBFeedParserProtocol.h" 11 | 12 | // Errors & codes 13 | extern NSString *const ADBErrorDomain; 14 | 15 | typedef NS_ENUM(NSInteger, ADBErrorCode) { 16 | ADBErrorCodeConnectionFailed, /* Connection failed */ 17 | ADBErrorCodeFeedParsingError, /* NSXMLParser encountered a parsing error */ 18 | ADBErrorCodeFeedValidationError, /* NSXMLParser encountered a validation error */ 19 | ADBErrorCodeGeneral /* ADBFeedParser general error */ 20 | }; 21 | 22 | @class ADBFeedParser; 23 | 24 | @interface ADBFeedParser : NSObject 25 | 26 | /** 27 | Designated initializer 28 | 29 | @return a ADBFeedReader object 30 | @param url, the url of the feed 31 | */ 32 | - (id)initWithURL:(NSURL *)url; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedParser.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBFeedParser.h" 10 | #import "ADBFeedInfoDTO.h" 11 | #import "ADBFeedItemDTO.h" 12 | #import "NSString+HTML.h" 13 | #import "NSDate+InternetDateTime.h" 14 | 15 | NSString *const ADBErrorDomain = @"ADBFeedParser"; 16 | 17 | @interface ADBFeedParser () 18 | 19 | @property (nonatomic, strong) ADBFeedInfoDTO *info; 20 | @property (nonatomic, strong) ADBFeedItemDTO *item; 21 | @property (nonatomic, strong) NSMutableString *currentText; 22 | @property (nonatomic, strong) NSString *currentPath; 23 | @property (nonatomic, strong) NSDictionary *currentElementAttributes; 24 | @property (nonatomic, strong) NSXMLParser *feedParser; 25 | @property (nonatomic, strong) NSString *connectionTextEncodingName; 26 | @property (nonatomic, strong) dispatch_queue_t workingQueue; 27 | 28 | @end 29 | 30 | @implementation ADBFeedParser 31 | 32 | @synthesize delegate = _delegate; 33 | @synthesize url = _url; 34 | 35 | - (id)init 36 | { 37 | [self doesNotRecognizeSelector:_cmd]; 38 | return nil; 39 | } 40 | 41 | - (id)initWithURL:(NSURL *)url 42 | { 43 | NSAssert(url, @"url parameter cannot be nil"); 44 | 45 | self = [super init]; 46 | 47 | if (self) { 48 | _url = [url copy]; 49 | _workingQueue = dispatch_queue_create("com.albertodebortoli.feedparser", DISPATCH_QUEUE_SERIAL); 50 | } 51 | 52 | return self; 53 | } 54 | 55 | #pragma mark - Public 56 | 57 | - (BOOL)start 58 | { 59 | [self _reset]; 60 | 61 | // Request 62 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.url 63 | cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData 64 | timeoutInterval:60]; 65 | [request setValue:@"ADBFeedParser" forHTTPHeaderField:@"User-Agent"]; 66 | 67 | [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 68 | if (!connectionError) { 69 | [self _startParsingData:data]; 70 | } 71 | else { 72 | [self _parsingFailedWithErrorCode:ADBErrorCodeConnectionFailed 73 | description:[NSString stringWithFormat:@"NSURLConnection failed at URL: %@", self.url]]; 74 | } 75 | }]; 76 | 77 | return YES; 78 | } 79 | 80 | - (void)stop 81 | { 82 | // Debug Log 83 | DLog(@"ADBFeedParser: Parsing stopped"); 84 | 85 | self.connectionTextEncodingName = nil; 86 | [self.feedParser abortParsing]; 87 | } 88 | 89 | #pragma mark - NSXMLParserDelegate 90 | 91 | - (void)parserDidStartDocument:(NSXMLParser *)parser 92 | { 93 | dispatch_async(dispatch_get_main_queue(), ^{ 94 | DLog(@"didStartDocument"); 95 | if ([self.delegate respondsToSelector:@selector(feedParserDidStart:)]) { 96 | [self.delegate feedParserDidStart:self]; 97 | } 98 | }); 99 | } 100 | 101 | - (void)parserDidEndDocument:(NSXMLParser *)parser 102 | { 103 | dispatch_async(dispatch_get_main_queue(), ^{ 104 | DLog(@"didEndDocument"); 105 | 106 | if ([self.delegate respondsToSelector:@selector(feedParserDidFinish:)]) { 107 | [self.delegate feedParserDidFinish:self]; 108 | } 109 | 110 | [self _reset]; 111 | }); 112 | } 113 | 114 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 115 | { 116 | dispatch_async(dispatch_get_main_queue(), ^{ 117 | DLog(@"parser:foundCharacters: %@", string); 118 | [self.currentText appendString:string]; 119 | }); 120 | } 121 | 122 | - (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock 123 | { 124 | dispatch_async(dispatch_get_main_queue(), ^{ 125 | DLog(@"NSXMLParser: foundCDATA (%d bytes)", CDATABlock.length); 126 | 127 | NSString *string = [[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding]; 128 | 129 | if (!string) { 130 | string = [[NSString alloc] initWithData:CDATABlock encoding:NSISOLatin1StringEncoding]; 131 | } 132 | 133 | if (string) { 134 | [self.currentText appendString:string]; 135 | } 136 | }); 137 | } 138 | 139 | - (void)parser:(NSXMLParser *)parser 140 | didStartElement:(NSString *)elementName 141 | namespaceURI:(NSString *)namespaceURI 142 | qualifiedName:(NSString *)qName 143 | attributes:(NSDictionary *)attributeDict 144 | { 145 | dispatch_async(dispatch_get_main_queue(), ^{ 146 | DLog(@"didStartElement: %@", elementName); 147 | 148 | // Adjust path 149 | self.currentPath = [self.currentPath stringByAppendingPathComponent:qName]; 150 | self.currentText = [NSMutableString string]; 151 | self.currentElementAttributes = attributeDict; 152 | 153 | // print all attributes for this element 154 | NSEnumerator *attrs = [attributeDict keyEnumerator]; 155 | NSString *key; 156 | // NSString *value; 157 | 158 | while((key = [attrs nextObject]) != nil) { 159 | //value = [attributeDict objectForKey:key]; 160 | DLog(@" attribute: %@ = %@", key, value); 161 | } 162 | }); 163 | } 164 | 165 | - (void)parser:(NSXMLParser *)parser 166 | didEndElement:(NSString *)elementName 167 | namespaceURI:(NSString *)namespaceURI 168 | qualifiedName:(NSString *)qName 169 | { 170 | dispatch_async(dispatch_get_main_queue(), ^{ 171 | DLog(@"didEndElement: %@", elementName); 172 | DLog(@"NSXMLParser: didEndElement: %@", qName); 173 | 174 | // Store data 175 | BOOL processed = NO; 176 | if (self.currentText) { 177 | 178 | // Remove newlines and whitespace from currentText 179 | NSString *trimmedText = [self.currentText stringByRemovingNewLinesAndWhitespace]; 180 | 181 | // Info 182 | if (!processed) { 183 | if ([self.currentPath isEqualToString:@"/rss/channel/title"]) { 184 | if (trimmedText.length) { 185 | self.info = [[ADBFeedInfoDTO alloc] init]; 186 | self.info.title = trimmedText; 187 | } 188 | processed = YES; 189 | } 190 | else if ([self.currentPath isEqualToString:@"/rss/channel/description"]) { 191 | if (trimmedText.length) { 192 | self.info.summary = trimmedText; 193 | } 194 | processed = YES; 195 | } 196 | else if ([self.currentPath isEqualToString:@"/rss/channel/link"]) { 197 | if (trimmedText.length) { 198 | self.info.link = trimmedText; 199 | } 200 | processed = YES; 201 | } 202 | } 203 | 204 | // Item 205 | if (!processed) { 206 | if ([self.currentPath isEqualToString:@"/rss/channel/item/title"]) { 207 | if (trimmedText.length) { 208 | self.item = [[ADBFeedItemDTO alloc] init]; 209 | self.item.title = trimmedText; 210 | } 211 | processed = YES; 212 | } 213 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/link"]) { 214 | if (trimmedText.length) { 215 | self.item.link = trimmedText; 216 | } 217 | processed = YES; 218 | } 219 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/guid"]) { 220 | if (trimmedText.length) { 221 | self.item.identifier = trimmedText; 222 | } 223 | processed = YES; 224 | } 225 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/description"]) { 226 | if (trimmedText.length) { 227 | self.item.summary = trimmedText; 228 | } 229 | processed = YES; 230 | } 231 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/media:thumbnail"]) { 232 | [self _processImageLink:self.currentElementAttributes addToObject:self.item]; 233 | processed = YES; 234 | } 235 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/content:encoded"]) { 236 | if (trimmedText.length) { 237 | self.item.content = trimmedText; 238 | } 239 | processed = YES; 240 | } 241 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/pubDate"]) { 242 | if (trimmedText.length) { 243 | self.item.date = [NSDate dateFromInternetDateTimeString:trimmedText formatHint:DateFormatHintRFC822]; 244 | } 245 | processed = YES; 246 | } 247 | else if ([self.currentPath isEqualToString:@"/rss/channel/item/dc:date"]) { 248 | if (trimmedText.length) { 249 | self.item.date = [NSDate dateFromInternetDateTimeString:trimmedText formatHint:DateFormatHintRFC3339]; 250 | } 251 | processed = YES; 252 | } 253 | } 254 | } 255 | 256 | // Adjust path 257 | self.currentPath = [self.currentPath stringByDeletingLastPathComponent]; 258 | 259 | // If end of an item then tell delegate 260 | if (!processed) { 261 | if ([qName isEqualToString:@"item"]) { 262 | // Dispatch item to delegate 263 | [self _dispatchFeedItemToDelegate]; 264 | } 265 | } 266 | 267 | // Check if the document has finished parsing and send off info if needed (i.e. there were no items) 268 | if (!processed) { 269 | if ([qName isEqualToString:@"rss"]) { 270 | // Document ending so if we havent sent off feed info yet, do so 271 | if (self.info) [self _dispatchFeedInfoToDelegate]; 272 | } 273 | } 274 | }); 275 | } 276 | 277 | // error handling 278 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError 279 | { 280 | dispatch_async(dispatch_get_main_queue(), ^{ 281 | DLog(@"XMLParser error: %@", [parseError localizedDescription]); 282 | [self _parsingFailedWithErrorCode:ADBErrorCodeFeedParsingError description:[parseError localizedDescription]]; 283 | }); 284 | } 285 | 286 | - (void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError 287 | { 288 | dispatch_async(dispatch_get_main_queue(), ^{ 289 | DLog(@"XMLParser error: %@", [validationError localizedDescription]); 290 | [self _parsingFailedWithErrorCode:ADBErrorCodeFeedValidationError description:[validationError localizedDescription]]; 291 | }); 292 | } 293 | 294 | #pragma mark - Private 295 | 296 | - (void)_startParsingData:(NSData *)data 297 | { 298 | if (data) { 299 | // Check whether it's UTF-8 300 | if (![[self.connectionTextEncodingName lowercaseString] isEqualToString:@"utf-8"]) { 301 | // Not UTF-8 so convert 302 | DLog(@"ADBFeedParser: XML document was not UTF-8... converting it..."); 303 | NSString *string = nil; 304 | 305 | // Attempt to detect encoding from response header 306 | NSStringEncoding nsEncoding = 0; 307 | if (self.connectionTextEncodingName) { 308 | CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.connectionTextEncodingName); 309 | if (cfEncoding != kCFStringEncodingInvalidId) { 310 | nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); 311 | if (nsEncoding != 0) string = [[NSString alloc] initWithData:data encoding:nsEncoding]; 312 | } 313 | } 314 | 315 | // If that failed then make our own attempts 316 | if (!string) { 317 | // http://www.mikeash.com/pyblog/friday-qa-2010-02-19-character-encodings.html 318 | string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 319 | if (!string) string = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding]; 320 | if (!string) string = [[NSString alloc] initWithData:data encoding:NSMacOSRomanStringEncoding]; 321 | } 322 | 323 | data = nil; 324 | 325 | // Parse 326 | if (string) { 327 | // Set XML encoding to UTF-8 328 | if ([string hasPrefix:@""]; 330 | if (a.location != NSNotFound) { 331 | NSString *xmlDec = [string substringToIndex:a.location]; 332 | if ([xmlDec rangeOfString:@"encoding=\"UTF-8\"" 333 | options:NSCaseInsensitiveSearch].location == NSNotFound) { 334 | NSRange b = [xmlDec rangeOfString:@"encoding=\""]; 335 | if (b.location != NSNotFound) { 336 | NSUInteger s = b.location+b.length; 337 | NSRange c = [xmlDec rangeOfString:@"\"" options:0 range:NSMakeRange(s, [xmlDec length] - s)]; 338 | if (c.location != NSNotFound) { 339 | NSString *temp = [string stringByReplacingCharactersInRange:NSMakeRange(b.location,c.location+c.length-b.location) 340 | withString:@"encoding=\"UTF-8\""]; 341 | string = temp; 342 | } 343 | } 344 | } 345 | } 346 | } 347 | 348 | // Convert string to UTF-8 data 349 | if (string) { 350 | data = [string dataUsingEncoding:NSUTF8StringEncoding]; 351 | } 352 | 353 | } 354 | 355 | } 356 | 357 | // Create NSXMLParser 358 | if (data) { 359 | NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data]; 360 | 361 | // this class will handle the events 362 | [xmlparser setDelegate:self]; 363 | [xmlparser setShouldResolveExternalEntities:NO]; 364 | [xmlparser setShouldProcessNamespaces:YES]; 365 | 366 | self.feedParser = xmlparser; 367 | dispatch_async(self.workingQueue, ^{ 368 | [self.feedParser parse]; 369 | }); 370 | } else { 371 | [self _parsingFailedWithErrorCode:ADBErrorCodeFeedParsingError description:@"Error with feed encoding"]; 372 | } 373 | } 374 | } 375 | 376 | - (void)_dispatchFeedInfoToDelegate 377 | { 378 | if (self.info) { 379 | // Inform delegate 380 | if ([self.delegate respondsToSelector:@selector(feedParser:didParseFeedInfo:)]) { 381 | [self.delegate feedParser:self didParseFeedInfo:self.info]; 382 | } 383 | 384 | // Debug log 385 | DLog(@"ADBFeedParser: info for \"%@\" successfully parsed", self.info.title); 386 | 387 | // Finish 388 | self.info = nil; 389 | } 390 | } 391 | 392 | - (void)_dispatchFeedItemToDelegate 393 | { 394 | if (self.item) { 395 | // Process before hand 396 | if (!self.item.summary) { 397 | self.item.summary = self.item.content; 398 | self.item.content = nil; 399 | } 400 | 401 | if (!self.item.date && self.item.update_date) { 402 | self.item.date = self.item.update_date; 403 | } 404 | 405 | // Debug log 406 | DLog(@"ADBFeedParser: item \"%@\" successfully parsed", self.item.title); 407 | 408 | // Inform delegate 409 | if ([self.delegate respondsToSelector:@selector(feedParser:didParseFeedItem:)]) 410 | [self.delegate feedParser:self didParseFeedItem:self.item]; 411 | 412 | // Finish 413 | self.item = nil; 414 | } 415 | } 416 | 417 | - (void)_reset 418 | { 419 | self.connectionTextEncodingName = nil; 420 | self.currentPath = @"/"; 421 | self.currentText = [[NSMutableString alloc] init]; 422 | self.item = nil; 423 | self.info = nil; 424 | self.currentElementAttributes = nil; 425 | } 426 | 427 | - (BOOL)_processImageLink:(NSDictionary *)attributes addToObject:(id)obj 428 | { 429 | if (attributes && [attributes objectForKey:@"url"]) { 430 | [obj setImage_url:[attributes objectForKey:@"url"]]; 431 | return YES; 432 | } 433 | return NO; 434 | } 435 | 436 | - (void)_parsingFailedWithErrorCode:(int)code description:(NSString *)description 437 | { 438 | // Create error 439 | NSError *error = [NSError errorWithDomain:ADBErrorDomain 440 | code:code 441 | userInfo:[NSDictionary dictionaryWithObject:description 442 | forKey:NSLocalizedDescriptionKey]]; 443 | DLog(@"%@", error); 444 | 445 | [self.feedParser abortParsing]; 446 | 447 | [self _reset]; 448 | 449 | if ([self.delegate respondsToSelector:@selector(feedParser:didFailWithError:)]) { 450 | [self.delegate feedParser:self didFailWithError:error]; 451 | } 452 | } 453 | 454 | @end 455 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedParserProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBFeedParserProtocol.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "ADBFeedInfoDTO.h" 12 | #import "ADBFeedItemDTO.h" 13 | 14 | @protocol ADBFeedParserDelegate; 15 | 16 | @protocol ADBFeedParserProtocol 17 | 18 | /** 19 | Makes a new request to the URL feed 20 | 21 | @return YES if request for the feed successfully started, NO otherwise 22 | */ 23 | - (BOOL)start; 24 | 25 | /** 26 | Cancels the request and abort parsing 27 | */ 28 | - (void)stop; 29 | 30 | @property (nonatomic, weak) id delegate; 31 | @property (nonatomic, strong) NSURL *url; 32 | 33 | @end 34 | 35 | /** 36 | Delegate 37 | */ 38 | @protocol ADBFeedParserDelegate 39 | @optional 40 | - (void)feedParserDidStart:(id)parser; 41 | - (void)feedParser:(id)parser didParseFeedInfo:(ADBFeedInfoDTO *)info; 42 | - (void)feedParser:(id)parser didParseFeedItem:(ADBFeedItemDTO *)item; 43 | - (void)feedParserDidFinish:(id)parser; 44 | - (void)feedParser:(id)parser didFailWithError:(NSError *)error; 45 | @end 46 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedReader-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | UINewsstandIcon 14 | 15 | CFBundleIconFiles 16 | 17 | 18 | 19 | UINewsstandBindingEdge 20 | UINewsstandBindingEdgeLeft 21 | UINewsstandBindingType 22 | UINewsstandBindingTypeMagazine 23 | 24 | 25 | CFBundleIcons~ipad 26 | 27 | UINewsstandIcon 28 | 29 | CFBundleIconFiles 30 | 31 | 32 | 33 | UINewsstandBindingEdge 34 | UINewsstandBindingEdgeLeft 35 | UINewsstandBindingType 36 | UINewsstandBindingTypeMagazine 37 | 38 | 39 | CFBundleIdentifier 40 | com.albertodebortoli.${PRODUCT_NAME:rfc1034identifier} 41 | CFBundleInfoDictionaryVersion 42 | 6.0 43 | CFBundleName 44 | ${PRODUCT_NAME} 45 | CFBundlePackageType 46 | APPL 47 | CFBundleShortVersionString 48 | 1.0 49 | CFBundleSignature 50 | ???? 51 | CFBundleVersion 52 | 1.0 53 | LSRequiresIPhoneOS 54 | 55 | UIRequiredDeviceCapabilities 56 | 57 | armv7 58 | 59 | UISupportedInterfaceOrientations 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBFeedReader-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'FeedReader' target in the 'FeedReader' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import 15 | #import "ADBMacros.h" 16 | #endif 17 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBImageView.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2012 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ADBImageView; 12 | 13 | @protocol ADBImageViewDelegate 14 | @optional 15 | - (void)adbImageView:(ADBImageView *)view willUpdateImage:(UIImage *)image; 16 | - (void)adbImageView:(ADBImageView *)view didLoadImage:(UIImage *)image; 17 | - (void)adbImageView:(ADBImageView *)view failedLoadingWithError:(NSError *)error; 18 | @end 19 | 20 | @interface ADBImageView : UIImageView 21 | 22 | @property (nonatomic, readonly) NSURL *url; 23 | @property (nonatomic, assign, getter = isCaching) BOOL caching; 24 | @property (nonatomic, assign) NSTimeInterval cacheTime; 25 | @property (nonatomic, strong) NSMutableData *data; 26 | @property (nonatomic, strong) UIImageView *placeholder; 27 | @property (nonatomic, weak) id delegate; 28 | 29 | - (void)setImageWithURL:(NSURL *)imageURL placeholderImage:(UIImage *)placeholderImage; 30 | - (void)cancelLoad; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBImageView.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2012 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBImageView.h" 10 | 11 | static CGFloat kADBImageViewTimoutInterval = 30.0; 12 | 13 | @interface ADBImageView (Private) 14 | - (void)_loadImage; 15 | @end 16 | 17 | @implementation ADBImageView { 18 | 19 | NSURL *_url; 20 | NSString *_cachePath; 21 | UIImageView *_placeholder; 22 | UIActivityIndicatorView *_activityIndicator; 23 | 24 | // Networking 25 | NSURLConnection *_connection; 26 | NSMutableData *_data; 27 | 28 | id __weak _delegate; 29 | } 30 | 31 | #pragma mark - Initializers 32 | 33 | - (id)initWithCoder:(NSCoder *)aDecoder 34 | { 35 | self = [super initWithCoder:aDecoder]; 36 | 37 | if (self) { 38 | self.clipsToBounds = YES; 39 | _caching = YES; 40 | 41 | [self setUserInteractionEnabled:YES]; 42 | [self setOpaque:YES]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (id)initWithFrame:(CGRect)frame 49 | { 50 | self = [super initWithFrame:frame]; 51 | 52 | if (self) { 53 | self.clipsToBounds = YES; 54 | self.caching = YES; 55 | 56 | [self setUserInteractionEnabled:YES]; 57 | [self setOpaque:YES]; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | #pragma mark - ADBImageView 64 | 65 | - (void)setImageWithURL:(NSURL *)imageURL placeholderImage:(UIImage *)placeholderImage; 66 | { 67 | // Defaults 68 | _url = imageURL; 69 | 70 | self.placeholder = [[UIImageView alloc] initWithImage:placeholderImage]; 71 | self.placeholder.frame = self.bounds; 72 | [self addSubview:self.placeholder]; 73 | 74 | [self _loadImage]; 75 | } 76 | 77 | - (void)reloadWithUrl:(NSURL *)url 78 | { 79 | [self cancelLoad]; 80 | self.image = nil; 81 | self.placeholder.hidden = NO; 82 | _url = url; 83 | [self _loadImage]; 84 | } 85 | 86 | - (void)cancelLoad 87 | { 88 | [_connection cancel]; 89 | _connection = nil; 90 | self.data = nil; 91 | } 92 | 93 | #pragma mark - NSURLConnection Delegates 94 | 95 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 96 | { 97 | self.data = [[NSMutableData alloc] initWithLength:0]; 98 | } 99 | 100 | - (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData 101 | { 102 | [self.data appendData:incrementalData]; 103 | } 104 | 105 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 106 | { 107 | [_activityIndicator stopAnimating]; 108 | 109 | if ([_delegate respondsToSelector:@selector(adbImageView:failedLoadingWithError:)]) { 110 | [_delegate adbImageView:self failedLoadingWithError:error]; 111 | } 112 | 113 | [_connection cancel]; 114 | _connection = nil; 115 | self.data = nil; 116 | } 117 | 118 | - (void)connectionDidFinishLoading:(NSURLConnection *)theConnection 119 | { 120 | [_activityIndicator stopAnimating]; 121 | 122 | UIImage *imageData = [UIImage imageWithData:self.data]; 123 | 124 | if ([_delegate respondsToSelector:@selector(adbImageView:willUpdateImage:)]) { 125 | [_delegate adbImageView:self willUpdateImage:imageData]; 126 | } 127 | 128 | self.image = imageData; 129 | self.placeholder.hidden = YES; 130 | 131 | [_connection cancel]; 132 | _connection = nil; 133 | self.data = nil; 134 | 135 | if ([_delegate respondsToSelector:@selector(adbImageView:didLoadImage:)]) { 136 | [_delegate adbImageView:self didLoadImage:imageData]; 137 | } 138 | } 139 | 140 | #pragma mark - Private methods 141 | 142 | - (void)_loadImage 143 | { 144 | self.image = nil; 145 | self.placeholder.hidden = NO; 146 | 147 | if ([[_url absoluteString] isEqualToString:@""]) { 148 | return; 149 | } 150 | 151 | _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 152 | CGPoint center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2); 153 | _activityIndicator.center = center; 154 | [self addSubview:_activityIndicator]; 155 | [_activityIndicator startAnimating]; 156 | 157 | NSURLRequest *request = [NSURLRequest requestWithURL:_url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:kADBImageViewTimoutInterval]; 158 | _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBMacros.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBMacros.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBAppDelegate.h" 10 | 11 | #ifdef DEBUG_LOG 12 | #define DLog(fmt, ...) NSLog((@"%s:%d " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); 13 | #else 14 | #define DLog(fmt, ...) // stubbed out 15 | #endif 16 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBMasterTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBMasterTableViewController.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIViewController+CoreData.h" 11 | #import "ADBFeedParserProtocol.h" 12 | #import "ADBImageView.h" 13 | 14 | @interface ADBMasterTableViewController : UITableViewController 15 | 16 | - (instancetype)initWithFeedParser:(id)feedParser; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBMasterTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBMasterTableViewController.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ADBMasterTableViewController.h" 13 | #import "ADBDetailTableViewController.h" 14 | #import "Reachability.h" 15 | #import "NSString+HTML.h" 16 | #import "FeedInfo+Additions.h" 17 | #import "FeedItem+Additions.h" 18 | #import "ADBFeedParser.h" 19 | #import "ADBFeedInfoDTO.h" 20 | #import "ADBFeedItemDTO.h" 21 | #import "ADBConstants.h" 22 | 23 | @interface ADBMasterTableViewController () < 24 | UISearchDisplayDelegate, 25 | ADBImageViewDelegate> 26 | 27 | @property (nonatomic, strong) NSArray *itemsToDisplay; 28 | @property (nonatomic, strong) NSMutableArray *parsedItems; 29 | @property (nonatomic, strong) NSArray *searchResults; 30 | @property (nonatomic, strong) NSDateFormatter *formatter; 31 | 32 | @property (nonatomic, strong) id feedParser; 33 | 34 | @end 35 | 36 | @implementation ADBMasterTableViewController 37 | 38 | - (instancetype)initWithFeedParser:(id)feedParser 39 | { 40 | NSAssert(feedParser != nil, @"feedParser parameter must not be nil"); 41 | 42 | self = [super init]; 43 | if (self) { 44 | _feedParser = feedParser; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)viewDidLoad 51 | { 52 | [super viewDidLoad]; 53 | 54 | self.automaticallyAdjustsScrollViewInsets = NO; 55 | 56 | if (self.navigationController) { 57 | self.tableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0); 58 | } 59 | 60 | // setup 61 | self.title = NSLocalizedString(@"Loading...", nil); 62 | self.formatter = [[NSDateFormatter alloc] init]; 63 | [self.formatter setDateStyle:NSDateFormatterShortStyle]; 64 | [self.formatter setTimeStyle:NSDateFormatterShortStyle]; 65 | self.parsedItems = [[NSMutableArray alloc] init]; 66 | self.itemsToDisplay = [NSArray array]; 67 | 68 | // refresh button 69 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh 70 | target:self 71 | action:@selector(refreshButtonPressed:)]; 72 | 73 | // Check for Internet connectivity 74 | Reachability *reach = [Reachability reachabilityForInternetConnection]; 75 | 76 | // No Internet connection 77 | if (reach.currentReachabilityStatus == NotReachable) { 78 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 79 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"FeedItem" inManagedObjectContext:self.managedObjectContext]; 80 | [fetchRequest setEntity:entity]; 81 | 82 | NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil]; 83 | if ([fetchedObjects count]) { 84 | NSMutableArray *dtoObjects = [NSMutableArray arrayWithCapacity:[fetchedObjects count]]; 85 | for (FeedItem *feedItem in fetchedObjects) { 86 | ADBFeedItemDTO *feedItemDTO = [feedItem dtoRepresentation]; 87 | [dtoObjects addObject:feedItemDTO]; 88 | } 89 | self.itemsToDisplay = dtoObjects; 90 | [self.tableView reloadData]; 91 | } 92 | } 93 | 94 | // Internet connection available 95 | else { 96 | [self _refreshFeed]; 97 | } 98 | } 99 | 100 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 101 | { 102 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 103 | } 104 | 105 | #pragma mark - ADBFeedParserDelegate 106 | 107 | - (void)feedParserDidStart:(ADBFeedParser *)parser 108 | { 109 | NSLog(@"Started Parsing: %@", parser.url); 110 | } 111 | 112 | - (void)feedParser:(ADBFeedParser *)parser didParseFeedInfo:(ADBFeedInfoDTO *)info 113 | { 114 | NSLog(@"Parsed Feed Info: “%@”", info.title); 115 | self.title = info.title; 116 | [self _persistedInfoObject:info]; 117 | } 118 | 119 | - (void)feedParser:(ADBFeedParser *)parser didParseFeedItem:(ADBFeedItemDTO *)item 120 | { 121 | NSLog(@"Parsed Feed Item: “%@”", item.title); 122 | if (item) { 123 | [self.parsedItems addObject:item]; 124 | } 125 | [self _persistedItemObject:item]; 126 | } 127 | 128 | - (void)feedParserDidFinish:(ADBFeedParser *)parser 129 | { 130 | NSLog(@"Finished Parsing"); 131 | [self _updateTableWithParsedItems]; 132 | 133 | NSError *error; 134 | if (![self.managedObjectContext save:&error]) { 135 | NSLog(@"Can't save: %@", [error localizedDescription]); 136 | } 137 | } 138 | 139 | - (void)feedParser:(ADBFeedParser *)parser didFailWithError:(NSError *)error 140 | { 141 | NSLog(@"Finished Parsing With Error: %@", error); 142 | 143 | if (self.parsedItems.count == 0) { 144 | self.title = NSLocalizedString(@"Failed", nil); // Show failed message in title 145 | } else { 146 | // Failed but some items parsed, so show and inform of error 147 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Parsing Incomplete", nil) 148 | message:NSLocalizedString(@"There was an error during the parsing of this feed. Not all of the feed items could parsed.", nil) 149 | delegate:nil 150 | cancelButtonTitle:NSLocalizedString(@"Dismiss", nil) 151 | otherButtonTitles:nil]; 152 | [alert show]; 153 | } 154 | 155 | [self _updateTableWithParsedItems]; 156 | } 157 | 158 | #pragma mark - Core Data 159 | 160 | - (BOOL)_persistedInfoObject:(ADBFeedInfoDTO *)feedInfo 161 | { 162 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 163 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"FeedInfo" inManagedObjectContext:self.managedObjectContext]; 164 | [fetchRequest setEntity:entity]; 165 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title == %@", feedInfo.title]; 166 | [fetchRequest setPredicate:predicate]; 167 | 168 | NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil]; 169 | 170 | FeedInfo *info = nil; 171 | 172 | // already existing on database 173 | if ([fetchedObjects count]) { 174 | info = [fetchedObjects lastObject]; 175 | } 176 | 177 | // not existing on database 178 | else { 179 | info = [NSEntityDescription insertNewObjectForEntityForName:@"FeedInfo" inManagedObjectContext:self.managedObjectContext]; 180 | } 181 | 182 | info.title = feedInfo.title; 183 | info.author = feedInfo.author; 184 | info.link = feedInfo.link; 185 | info.summary = feedInfo.summary; 186 | 187 | return [self.managedObjectContext save:nil]; 188 | } 189 | 190 | - (BOOL)_persistedItemObject:(ADBFeedItemDTO *)feedItem 191 | { 192 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 193 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"FeedItem" inManagedObjectContext:self.managedObjectContext]; 194 | [fetchRequest setEntity:entity]; 195 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title == %@", feedItem.title]; 196 | [fetchRequest setPredicate:predicate]; 197 | 198 | NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil]; 199 | 200 | FeedItem *item = nil; 201 | 202 | // already existing on database 203 | if ([fetchedObjects count]) { 204 | item = [fetchedObjects lastObject]; 205 | } 206 | 207 | // not existing on database 208 | else { 209 | item = [NSEntityDescription insertNewObjectForEntityForName:@"FeedItem" inManagedObjectContext:self.managedObjectContext]; 210 | } 211 | 212 | item.title = feedItem.title; 213 | item.summary = feedItem.summary; 214 | item.link = feedItem.link; 215 | item.content = feedItem.content; 216 | item.identifier = feedItem.identifier; 217 | item.image_url = feedItem.image_url; 218 | item.update_date = feedItem.update_date; 219 | item.date = feedItem.date; 220 | 221 | return [self.managedObjectContext save:nil]; 222 | } 223 | 224 | #pragma mark - TableViewDataSource 225 | 226 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 227 | { 228 | // Return the number of sections. 229 | return 1; 230 | } 231 | 232 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 233 | { 234 | if (tableView == self.searchDisplayController.searchResultsTableView) { 235 | return [self.searchResults count]; 236 | } else { 237 | // Return the number of rows in the section. 238 | return [self.itemsToDisplay count]; 239 | } 240 | } 241 | 242 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 243 | { 244 | static NSString *CellIdentifier = @"Cell"; 245 | 246 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 247 | if (cell == nil) { 248 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 249 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 250 | } else { 251 | for (UIView *view in cell.imageView.subviews) { 252 | [view removeFromSuperview]; 253 | } 254 | } 255 | 256 | FeedItem *item = nil; 257 | 258 | if (tableView == self.searchDisplayController.searchResultsTableView) { 259 | item = [self.searchResults objectAtIndex:indexPath.row]; 260 | } else { 261 | item = [self.itemsToDisplay objectAtIndex:indexPath.row]; 262 | } 263 | 264 | if (item) { 265 | // Process 266 | NSString *itemTitle = item.title ?: NSLocalizedString(@"[No Title]", nil); 267 | NSString *itemSummary = item.summary ? item.summary : NSLocalizedString(@"[No Summary]", nil); 268 | 269 | // Set 270 | cell.textLabel.font = [UIFont boldSystemFontOfSize:15]; 271 | cell.textLabel.text = itemTitle; 272 | NSMutableString *subtitle = [NSMutableString string]; 273 | if (item.date) [subtitle appendFormat:@"%@: ", [self.formatter stringFromDate:item.date]]; 274 | [subtitle appendString:itemSummary]; 275 | cell.detailTextLabel.text = subtitle; 276 | 277 | // Load image 278 | ADBImageView *imageView = [[ADBImageView alloc] initWithFrame:CGRectMake(0, 0, 43, 43)]; 279 | imageView.contentMode = UIViewContentModeScaleAspectFill; 280 | imageView.delegate = self; 281 | imageView.caching = NO; 282 | 283 | // placeholder 284 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"icon" ofType:@"png"]; 285 | UIImage *image = [UIImage imageWithContentsOfFile:filePath]; 286 | 287 | // item media URL 288 | NSURL *imageURL = [NSURL URLWithString:item.image_url]; 289 | [imageView setImageWithURL:imageURL placeholderImage:image]; 290 | 291 | cell.imageView.image = image; 292 | [cell.imageView addSubview:imageView]; 293 | } 294 | 295 | return cell; 296 | } 297 | 298 | #pragma mark - TableViewDelegate 299 | 300 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 301 | { 302 | ADBDetailTableViewController *detail = [[ADBDetailTableViewController alloc] initWithStyle:UITableViewStyleGrouped]; 303 | detail.item = (ADBFeedItemDTO *)[self.itemsToDisplay objectAtIndex:indexPath.row]; 304 | [self.navigationController pushViewController:detail animated:YES]; 305 | 306 | // Deselect 307 | [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; 308 | } 309 | 310 | #pragma mark - ADBImageViewDelegate 311 | 312 | - (void)adbImageView:(ADBImageView *)view willUpdateImage:(UIImage *)image 313 | { 314 | view.alpha = 0.0; 315 | [UIView animateWithDuration:0.7 animations:^{ view.alpha = 1.0; }]; 316 | } 317 | 318 | #pragma mark - UISearchDisplayDelegate 319 | 320 | - (BOOL)searchDisplayController:(UISearchDisplayController *)controller 321 | shouldReloadTableForSearchString:(NSString *)searchString 322 | { 323 | [self _filterContentForSearchText:searchString 324 | scope:[[self.searchDisplayController.searchBar scopeButtonTitles] 325 | objectAtIndex:[self.searchDisplayController.searchBar 326 | selectedScopeButtonIndex]]]; 327 | 328 | return YES; 329 | } 330 | 331 | #pragma mark - Storyboard 332 | 333 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 334 | { 335 | if ([segue.identifier isEqualToString:@"detailItem"]) { 336 | UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", nil) 337 | style:UIBarButtonItemStyleBordered 338 | target:nil 339 | action:nil]; 340 | [[self navigationItem] setBackBarButtonItem:backButton]; 341 | ADBDetailTableViewController *detailViewController = segue.destinationViewController; 342 | detailViewController.managedObjectContext = self.managedObjectContext; 343 | 344 | NSIndexPath *indexPath = nil; 345 | ADBFeedItemDTO *feedItem = nil; 346 | 347 | if ([self.searchDisplayController isActive]) { 348 | indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow]; 349 | feedItem = [self.searchResults objectAtIndex:indexPath.row]; 350 | } else { 351 | indexPath = [self.tableView indexPathForSelectedRow]; 352 | feedItem = [self.itemsToDisplay objectAtIndex:indexPath.row]; 353 | } 354 | 355 | detailViewController.item = feedItem; 356 | } 357 | } 358 | 359 | #pragma mark - Actions 360 | 361 | - (void)refreshButtonPressed:(id)sender 362 | { 363 | [self _refreshFeed]; 364 | } 365 | 366 | #pragma mark - Private 367 | 368 | - (void)_refreshFeed 369 | { 370 | Reachability *reach = [Reachability reachabilityForInternetConnection]; 371 | 372 | // No Internet connection 373 | if (reach.currentReachabilityStatus == NotReachable) { 374 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"No Internet", nil) 375 | message:NSLocalizedString(@"Whoops, missing internet connection!", nil) 376 | delegate:nil 377 | cancelButtonTitle:NSLocalizedString(@"Ah, I see... x_x", nil) 378 | otherButtonTitles:nil]; 379 | [alertView show]; 380 | } 381 | 382 | // Internet connection available 383 | else { 384 | self.title = NSLocalizedString(@"Refreshing...", nil); 385 | [self.parsedItems removeAllObjects]; 386 | [self.feedParser stop]; 387 | [self.feedParser start]; 388 | self.tableView.userInteractionEnabled = NO; 389 | } 390 | } 391 | 392 | - (void)_filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope 393 | { 394 | NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"SELF.title contains[cd] %@", searchText]; 395 | 396 | self.searchResults = [self.itemsToDisplay filteredArrayUsingPredicate:resultPredicate]; 397 | } 398 | 399 | - (void)_updateTableWithParsedItems 400 | { 401 | self.itemsToDisplay = [self.parsedItems sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:NO]]]; 402 | self.tableView.userInteractionEnabled = YES; 403 | self.tableView.alpha = 1; 404 | [self.tableView reloadData]; 405 | } 406 | 407 | @end 408 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBMasterTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBWebBrowserViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADBWebBrowserViewController.h 3 | // ADBWebBrowserViewController 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2012 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ADBWebBrowserViewController; 12 | 13 | @protocol ADBWebBrowserViewControllerDelegate 14 | @optional 15 | - (void)webBrowserViewControllerDidDismiss:(ADBWebBrowserViewController *)controller; 16 | - (void)webBrowserViewController:(ADBWebBrowserViewController *)controller didRequestURL:(NSURL *)url; 17 | @end 18 | 19 | @interface ADBWebBrowserViewController : UIViewController { 20 | UIWebView *_webView; 21 | UITextField *_addressField; 22 | NSURL *_urlAddress; 23 | id _delegate; 24 | } 25 | 26 | - (id)initWithURL:(NSURL *)aUrlAddress delegate:(id )aDelegate; 27 | - (IBAction)doneButtonPress:(id)sender; 28 | 29 | @property (nonatomic, strong) IBOutlet UIWebView *webView; 30 | @property (nonatomic, strong) IBOutlet UITextField *addressField; 31 | @property (nonatomic) id delegate; 32 | @property (nonatomic, strong) NSURL *urlAddress; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBWebBrowserViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ADBWebBrowserViewController.m 3 | // ADBWebBrowserViewController 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2012 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "ADBWebBrowserViewController.h" 10 | #import "MBProgressHUD.h" 11 | 12 | @implementation ADBWebBrowserViewController 13 | 14 | #pragma mark - View lifecycle 15 | 16 | - (id)initWithURL:(NSURL *)aUrlAddress delegate:(id )aDelegate 17 | { 18 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { 19 | self = [super initWithNibName:@"ADBWebBrowserViewController_iPhone" bundle:nil]; 20 | } else { 21 | self = [super initWithNibName:@"ADBWebBrowserViewController_iPad" bundle:nil]; 22 | } 23 | 24 | if (self) { 25 | _delegate = aDelegate; 26 | _urlAddress = aUrlAddress; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)viewDidLoad 32 | { 33 | [super viewDidLoad]; 34 | self.addressField.text = [self.urlAddress absoluteString]; 35 | } 36 | 37 | - (void)viewDidAppear:(BOOL)animated 38 | { 39 | [super viewDidAppear:animated]; 40 | [self.webView loadRequest:[NSURLRequest requestWithURL:self.urlAddress]]; 41 | } 42 | 43 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 44 | { 45 | return YES; 46 | } 47 | 48 | #pragma mark - Actions 49 | 50 | - (IBAction)doneButtonPress:(id)sender 51 | { 52 | if ([self.delegate respondsToSelector:@selector(webBrowserViewControllerDidDismiss:)]) { 53 | [self.delegate webBrowserViewControllerDidDismiss:self]; 54 | } 55 | [self dismissViewControllerAnimated:YES completion:nil]; 56 | } 57 | 58 | #pragma mark - UIWebViewDelegate 59 | 60 | - (void)webViewDidStartLoad:(UIWebView *)webView 61 | { 62 | [MBProgressHUD showHUDAddedTo:self.webView animated:YES]; 63 | if ([self.delegate respondsToSelector:@selector(webBrowserViewController:didRequestURL:)]) { 64 | [self.delegate webBrowserViewController:self didRequestURL:self.urlAddress]; 65 | } 66 | } 67 | 68 | - (void)webViewDidFinishLoad:(UIWebView *)webView 69 | { 70 | [MBProgressHUD hideAllHUDsForView:self.webView animated:YES]; 71 | self.addressField.text = [[self.webView.request URL] absoluteString]; 72 | } 73 | 74 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 75 | { 76 | [MBProgressHUD hideAllHUDsForView:self.webView animated:YES]; 77 | self.addressField.text = [[self.webView.request URL] absoluteString]; 78 | } 79 | 80 | #pragma mark - UITextFieldDelegate 81 | 82 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 83 | { 84 | self.urlAddress = [NSURL URLWithString:textField.text]; 85 | [self.webView loadRequest:[NSURLRequest requestWithURL:self.urlAddress]]; 86 | [textField resignFirstResponder]; 87 | return YES; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBWebBrowserViewController_iPad.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1296 5 | 12C60 6 | 2843 7 | 1187.34 8 | 625.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1929 12 | 13 | 14 | IBProxyObject 15 | IBUIBarButtonItem 16 | IBUITextField 17 | IBUIToolbar 18 | IBUIView 19 | IBUIWebView 20 | 21 | 22 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | 30 | IBFilesOwner 31 | IBIPadFramework 32 | 33 | 34 | IBFirstResponder 35 | IBIPadFramework 36 | 37 | 38 | 39 | 292 40 | 41 | 42 | 43 | 258 44 | 45 | 46 | 47 | 292 48 | {{12, 7}, {526, 31}} 49 | 50 | 51 | _NS:9 52 | NO 53 | YES 54 | IBIPadFramework 55 | 0 56 | 57 | 3 58 | 59 | 3 60 | MAA 61 | 62 | 2 63 | 64 | 65 | YES 66 | 17 67 | 68 | IBCocoaTouchFramework 69 | 70 | 71 | 1 72 | 14 73 | 74 | 75 | Helvetica 76 | 14 77 | 16 78 | 79 | 80 | 81 | {768, 44} 82 | 83 | 84 | _NS:9 85 | NO 86 | NO 87 | IBIPadFramework 88 | 89 | 90 | IBIPadFramework 91 | 92 | 93 | 94 | 95 | IBIPadFramework 96 | 97 | 5 98 | 99 | 100 | IBIPadFramework 101 | 1 102 | 103 | 13 104 | 105 | 106 | IBIPadFramework 107 | 1 108 | 109 | 14 110 | 111 | 112 | IBIPadFramework 113 | 1 114 | 115 | 0 116 | 117 | 118 | 119 | 120 | 121 | 274 122 | {{0, 44}, {768, 960}} 123 | 124 | _NS:9 125 | 126 | 1 127 | MSAxIDEAA 128 | 129 | IBIPadFramework 130 | 1 131 | YES 132 | 133 | 134 | {{0, 20}, {768, 1004}} 135 | 136 | 137 | 3 138 | MQA 139 | 140 | 141 | NO 142 | 143 | 2 144 | 145 | IBIPadFramework 146 | 147 | 148 | 149 | 150 | 151 | 152 | view 153 | 154 | 155 | 156 | 3 157 | 158 | 159 | 160 | webView 161 | 162 | 163 | 164 | 18 165 | 166 | 167 | 168 | addressField 169 | 170 | 171 | 172 | 20 173 | 174 | 175 | 176 | delegate 177 | 178 | 179 | 180 | 19 181 | 182 | 183 | 184 | reload 185 | 186 | 187 | 188 | 15 189 | 190 | 191 | 192 | stopLoading 193 | 194 | 195 | 196 | 16 197 | 198 | 199 | 200 | doneButtonPress: 201 | 202 | 203 | 204 | 23 205 | 206 | 207 | 208 | delegate 209 | 210 | 211 | 212 | 22 213 | 214 | 215 | 216 | 217 | 218 | 0 219 | 220 | 221 | 222 | 223 | 224 | -1 225 | 226 | 227 | File's Owner 228 | 229 | 230 | -2 231 | 232 | 233 | 234 | 235 | 2 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 4 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 7 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 6 265 | 266 | 267 | 268 | 269 | 8 270 | 271 | 272 | 273 | 274 | 9 275 | 276 | 277 | 278 | 279 | 11 280 | 281 | 282 | 283 | 284 | 12 285 | 286 | 287 | 288 | 289 | 13 290 | 291 | 292 | 293 | 294 | 295 | 296 | ADBWebBrowserViewController 297 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 298 | UIResponder 299 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 300 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 301 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 302 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 303 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 304 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 305 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 306 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 307 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 308 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 309 | 310 | 311 | 312 | 313 | 314 | 23 315 | 316 | 317 | 0 318 | IBIPadFramework 319 | 320 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 321 | 322 | 323 | YES 324 | 3 325 | 1929 326 | 327 | 328 | -------------------------------------------------------------------------------- /ADBFeedReader/ADBWebBrowserViewController_iPhone.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1296 5 | 12C60 6 | 2843 7 | 1187.34 8 | 625.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1929 12 | 13 | 14 | IBProxyObject 15 | IBUIBarButtonItem 16 | IBUITextField 17 | IBUIToolbar 18 | IBUIView 19 | IBUIWebView 20 | 21 | 22 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | 30 | IBFilesOwner 31 | IBCocoaTouchFramework 32 | 33 | 34 | IBFirstResponder 35 | IBCocoaTouchFramework 36 | 37 | 38 | 39 | 292 40 | 41 | 42 | 43 | 258 44 | 45 | 46 | 47 | 292 48 | {{12, 7}, {141, 31}} 49 | 50 | _NS:9 51 | NO 52 | YES 53 | IBCocoaTouchFramework 54 | 0 55 | 56 | 3 57 | 58 | 3 59 | MAA 60 | 61 | 2 62 | 63 | 64 | YES 65 | 17 66 | 67 | IBCocoaTouchFramework 68 | 69 | 70 | 1 71 | 14 72 | 73 | 74 | Helvetica 75 | 14 76 | 16 77 | 78 | 79 | 80 | {320, 44} 81 | 82 | 83 | _NS:9 84 | NO 85 | NO 86 | IBCocoaTouchFramework 87 | 88 | 89 | IBCocoaTouchFramework 90 | 91 | 92 | 93 | 94 | IBCocoaTouchFramework 95 | 96 | 5 97 | 98 | 99 | IBCocoaTouchFramework 100 | 1 101 | 102 | 13 103 | 104 | 105 | IBCocoaTouchFramework 106 | 1 107 | 108 | 14 109 | 110 | 111 | IBCocoaTouchFramework 112 | 1 113 | 114 | 0 115 | 116 | 117 | 118 | 119 | 120 | 292 121 | {{0, 44}, {320, 416}} 122 | 123 | _NS:9 124 | 125 | 1 126 | MSAxIDEAA 127 | 128 | IBCocoaTouchFramework 129 | 1 130 | YES 131 | 132 | 133 | {320, 460} 134 | 135 | _NS:9 136 | 137 | 3 138 | MQA 139 | 140 | 141 | IBCocoaTouchFramework 142 | 143 | 144 | 145 | 146 | 147 | 148 | view 149 | 150 | 151 | 152 | 10 153 | 154 | 155 | 156 | webView 157 | 158 | 159 | 160 | 15 161 | 162 | 163 | 164 | addressField 165 | 166 | 167 | 168 | 18 169 | 170 | 171 | 172 | doneButtonPress: 173 | 174 | 175 | 176 | 19 177 | 178 | 179 | 180 | stopLoading 181 | 182 | 183 | 184 | 17 185 | 186 | 187 | 188 | reload 189 | 190 | 191 | 192 | 16 193 | 194 | 195 | 196 | delegate 197 | 198 | 199 | 200 | 11 201 | 202 | 203 | 204 | delegate 205 | 206 | 207 | 208 | 14 209 | 210 | 211 | 212 | 213 | 214 | 0 215 | 216 | 217 | 218 | 219 | 220 | -1 221 | 222 | 223 | File's Owner 224 | 225 | 226 | -2 227 | 228 | 229 | 230 | 231 | 2 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 3 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 8 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 7 261 | 262 | 263 | 264 | 265 | 6 266 | 267 | 268 | 269 | 270 | 5 271 | 272 | 273 | 274 | 275 | 4 276 | 277 | 278 | 279 | 280 | 9 281 | 282 | 283 | 284 | 285 | 13 286 | 287 | 288 | 289 | 290 | 291 | 292 | ADBWebBrowserViewController 293 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 294 | UIResponder 295 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 296 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 297 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 298 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 299 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 300 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 301 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 302 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 303 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 304 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 305 | 306 | 307 | 308 | 309 | 310 | 19 311 | 312 | 313 | 0 314 | IBCocoaTouchFramework 315 | 316 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 317 | 318 | 319 | YES 320 | 3 321 | 1929 322 | 323 | 324 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedInfo+Additions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedInfo+Additions.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FeedInfo.h" 10 | 11 | @class ADBFeedInfoDTO; 12 | 13 | @interface FeedInfo (Additions) 14 | 15 | - (ADBFeedInfoDTO *)dtoRepresentation; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedInfo+Additions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FeedInfo+Additions.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FeedInfo+Additions.h" 10 | #import "ADBFeedInfoDTO.h" 11 | 12 | @implementation FeedInfo (Additions) 13 | 14 | - (ADBFeedInfoDTO *)dtoRepresentation 15 | { 16 | ADBFeedInfoDTO *retVal = [[ADBFeedInfoDTO alloc] init]; 17 | retVal.title = self.title; 18 | retVal.author = self.author; 19 | retVal.link = self.link; 20 | retVal.summary = self.summary; 21 | return retVal; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedInfo.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface FeedInfo : NSManagedObject 13 | 14 | @property (nonatomic, copy) NSString *title; 15 | @property (nonatomic, copy) NSString *author; 16 | @property (nonatomic, copy) NSString *link; 17 | @property (nonatomic, copy) NSString *summary; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // FeedInfo.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "FeedInfo.h" 10 | 11 | @implementation FeedInfo 12 | 13 | @dynamic title; 14 | @dynamic author; 15 | @dynamic link; 16 | @dynamic summary; 17 | 18 | - (NSString *)description 19 | { 20 | return [NSString stringWithFormat:@"<%@: %p> title: %@", [self class], self, self.title]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedItem+Additions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem+Additions.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FeedItem.h" 10 | 11 | @class ADBFeedItemDTO; 12 | 13 | @interface FeedItem (Additions) 14 | 15 | - (ADBFeedItemDTO *)dtoRepresentation; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedItem+Additions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem+Additions.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 06/04/2014. 6 | // Copyright (c) 2014 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FeedItem+Additions.h" 10 | #import "ADBFeedItemDTO.h" 11 | 12 | @implementation FeedItem (Additions) 13 | 14 | - (ADBFeedItemDTO *)dtoRepresentation 15 | { 16 | ADBFeedItemDTO *retVal = [[ADBFeedItemDTO alloc] init]; 17 | retVal.title = self.title; 18 | retVal.summary = self.summary; 19 | retVal.link = self.link; 20 | retVal.content = self.content; 21 | retVal.identifier = self.identifier; 22 | retVal.image_url = self.image_url; 23 | retVal.update_date = self.update_date; 24 | retVal.date = self.date; 25 | return retVal; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface FeedItem : NSManagedObject 13 | 14 | @property (nonatomic, copy) NSString *title; 15 | @property (nonatomic, copy) NSString *summary; 16 | @property (nonatomic, copy) NSString *link; 17 | @property (nonatomic, copy) NSString *content; 18 | @property (nonatomic, copy) NSString *identifier; 19 | @property (nonatomic, copy) NSString *image_url; 20 | @property (nonatomic, strong) NSDate *update_date; 21 | @property (nonatomic, strong) NSDate *date; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "FeedItem.h" 10 | 11 | @implementation FeedItem 12 | 13 | @dynamic title; 14 | @dynamic summary; 15 | @dynamic link; 16 | @dynamic content; 17 | @dynamic identifier; 18 | @dynamic image_url; 19 | @dynamic update_date; 20 | @dynamic date; 21 | 22 | - (NSString *)description 23 | { 24 | return [NSString stringWithFormat:@"<%@: %p> title: %@", [self class], self, self.title]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedReader.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ADBFeedReader/FeedReader.xcdatamodeld/FeedReader.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ADBFeedReader/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "60x60" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "2x", 11 | "size" : "40x40" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ADBFeedReader/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "minimum-system-version" : "7.0", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "orientation" : "portrait", 11 | "idiom" : "iphone", 12 | "minimum-system-version" : "7.0", 13 | "subtype" : "retina4", 14 | "scale" : "2x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ADBFeedReader/NSDate+InternetDateTime.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+InternetDateTime.h 3 | // Nuxie 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright 2011 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // Formatting hints 12 | typedef enum { 13 | DateFormatHintNone, 14 | DateFormatHintRFC822, 15 | DateFormatHintRFC3339 16 | } DateFormatHint; 17 | 18 | // A category to parse internet date & time strings 19 | @interface NSDate (InternetDateTime) 20 | 21 | // Get date from RFC3339 or RFC822 string 22 | // - A format/specification hint can be used to speed up, 23 | // otherwise both will be attempted in order to get a date 24 | + (NSDate *)dateFromInternetDateTimeString:(NSString *)dateString 25 | formatHint:(DateFormatHint)hint; 26 | 27 | // Get date from a string using a specific date specification 28 | + (NSDate *)dateFromRFC3339String:(NSString *)dateString; 29 | + (NSDate *)dateFromRFC822String:(NSString *)dateString; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ADBFeedReader/NSDate+InternetDateTime.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+InternetDateTime.m 3 | // Nuxie 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright 2011 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "NSDate+InternetDateTime.h" 10 | 11 | // Always keep the formatter around as they're expensive to instantiate 12 | static NSDateFormatter *_internetDateTimeFormatter = nil; 13 | 14 | // Good info on internet dates here: 15 | // http://developer.apple.com/iphone/library/qa/qa2010/qa1480.html 16 | @implementation NSDate (InternetDateTime) 17 | 18 | // Instantiate single date formatter 19 | + (NSDateFormatter *)internetDateTimeFormatter 20 | { 21 | @synchronized(self) { 22 | if (!_internetDateTimeFormatter) { 23 | NSLocale *en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 24 | _internetDateTimeFormatter = [[NSDateFormatter alloc] init]; 25 | [_internetDateTimeFormatter setLocale:en_US_POSIX]; 26 | [_internetDateTimeFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 27 | } 28 | } 29 | return _internetDateTimeFormatter; 30 | } 31 | 32 | // Get a date from a string - hint can be used to speed up 33 | + (NSDate *)dateFromInternetDateTimeString:(NSString *)dateString formatHint:(DateFormatHint)hint 34 | { 35 | NSDate *date = nil; 36 | if (dateString) { 37 | if (hint != DateFormatHintRFC3339) { 38 | // Try RFC822 first 39 | date = [NSDate dateFromRFC822String:dateString]; 40 | if (!date) date = [NSDate dateFromRFC3339String:dateString]; 41 | } else { 42 | // Try RFC3339 first 43 | date = [NSDate dateFromRFC3339String:dateString]; 44 | if (!date) date = [NSDate dateFromRFC822String:dateString]; 45 | } 46 | } 47 | return date; 48 | } 49 | 50 | // See http://www.faqs.org/rfcs/rfc822.html 51 | + (NSDate *)dateFromRFC822String:(NSString *)dateString 52 | { 53 | NSDate *date = nil; 54 | if (dateString) { 55 | NSDateFormatter *dateFormatter = [NSDate internetDateTimeFormatter]; 56 | @synchronized(dateFormatter) { 57 | 58 | // Process 59 | NSString *RFC822String = [[NSString stringWithString:dateString] uppercaseString]; 60 | if ([RFC822String rangeOfString:@","].location != NSNotFound) { 61 | if (!date) { // Sun, 19 May 2002 15:21:36 GMT 62 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss zzz"]; 63 | date = [dateFormatter dateFromString:RFC822String]; 64 | } 65 | if (!date) { // Sun, 19 May 2002 15:21 GMT 66 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm zzz"]; 67 | date = [dateFormatter dateFromString:RFC822String]; 68 | } 69 | if (!date) { // Sun, 19 May 2002 15:21:36 70 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss"]; 71 | date = [dateFormatter dateFromString:RFC822String]; 72 | } 73 | if (!date) { // Sun, 19 May 2002 15:21 74 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm"]; 75 | date = [dateFormatter dateFromString:RFC822String]; 76 | } 77 | } else { 78 | if (!date) { // 19 May 2002 15:21:36 GMT 79 | [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss zzz"]; 80 | date = [dateFormatter dateFromString:RFC822String]; 81 | } 82 | if (!date) { // 19 May 2002 15:21 GMT 83 | [dateFormatter setDateFormat:@"d MMM yyyy HH:mm zzz"]; 84 | date = [dateFormatter dateFromString:RFC822String]; 85 | } 86 | if (!date) { // 19 May 2002 15:21:36 87 | [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss"]; 88 | date = [dateFormatter dateFromString:RFC822String]; 89 | } 90 | if (!date) { // 19 May 2002 15:21 91 | [dateFormatter setDateFormat:@"d MMM yyyy HH:mm"]; 92 | date = [dateFormatter dateFromString:RFC822String]; 93 | } 94 | } 95 | if (!date) NSLog(@"Could not parse RFC822 date: \"%@\" Possible invalid format.", dateString); 96 | 97 | } 98 | } 99 | return date; 100 | } 101 | 102 | // See http://www.faqs.org/rfcs/rfc3339.html 103 | + (NSDate *)dateFromRFC3339String:(NSString *)dateString 104 | { 105 | NSDate *date = nil; 106 | if (dateString) { 107 | NSDateFormatter *dateFormatter = [NSDate internetDateTimeFormatter]; 108 | @synchronized(dateFormatter) { 109 | 110 | // Process date 111 | NSString *RFC3339String = [[NSString stringWithString:dateString] uppercaseString]; 112 | RFC3339String = [RFC3339String stringByReplacingOccurrencesOfString:@"Z" withString:@"-0000"]; 113 | // Remove colon in timezone as it breaks NSDateFormatter in iOS 4+. 114 | // - see https://devforums.apple.com/thread/45837 115 | if (RFC3339String.length > 20) { 116 | RFC3339String = [RFC3339String stringByReplacingOccurrencesOfString:@":" 117 | withString:@"" 118 | options:0 119 | range:NSMakeRange(20, RFC3339String.length-20)]; 120 | } 121 | if (!date) { // 1996-12-19T16:39:57-0800 122 | [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZ"]; 123 | date = [dateFormatter dateFromString:RFC3339String]; 124 | } 125 | if (!date) { // 1937-01-01T12:00:27.87+0020 126 | [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZZZ"]; 127 | date = [dateFormatter dateFromString:RFC3339String]; 128 | } 129 | if (!date) { // 1937-01-01T12:00:27 130 | [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss"]; 131 | date = [dateFormatter dateFromString:RFC3339String]; 132 | } 133 | if (!date) NSLog(@"Could not parse RFC3339 date: \"%@\" Possible invalid format.", dateString); 134 | 135 | } 136 | } 137 | 138 | return date; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /ADBFeedReader/NSString+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+HTML.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (HTML) 12 | 13 | /** 14 | Remove newlines and white space from string. 15 | 16 | @return A new string without white spaces and newlines 17 | */ 18 | - (NSString *)stringByRemovingNewLinesAndWhitespace; 19 | 20 | @end -------------------------------------------------------------------------------- /ADBFeedReader/NSString+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+HTML.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import "NSString+HTML.h" 10 | 11 | @implementation NSString (HTML) 12 | 13 | #pragma mark - Instance Methods 14 | 15 | - (NSString *)stringByRemovingNewLinesAndWhitespace 16 | { 17 | return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ADBFeedReader/Reachability/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.h 4 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 5 | 6 | Version: 2.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | 49 | #import 50 | #import 51 | 52 | #import 53 | #import 54 | #import 55 | #import 56 | #import 57 | #import 58 | 59 | typedef enum { 60 | NotReachable = 0, 61 | ReachableViaWiFi, 62 | ReachableViaWWAN 63 | } NetworkStatus; 64 | #define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" 65 | 66 | @interface Reachability: NSObject 67 | { 68 | BOOL localWiFiRef; 69 | SCNetworkReachabilityRef reachabilityRef; 70 | } 71 | 72 | //reachabilityWithHostName- Use to check the reachability of a particular host name. 73 | + (Reachability*) reachabilityWithHostName: (NSString*) hostName; 74 | 75 | //reachabilityWithAddress- Use to check the reachability of a particular IP address. 76 | + (Reachability*) reachabilityWithAddress: (const struct sockaddr_in *) hostAddress; 77 | 78 | //reachabilityForInternetConnection- checks whether the default route is available. 79 | // Should be used by applications that do not connect to a particular host 80 | + (Reachability*) reachabilityForInternetConnection; 81 | 82 | //reachabilityForLocalWiFi- checks whether a local wifi connection is available. 83 | + (Reachability*) reachabilityForLocalWiFi; 84 | 85 | //Start listening for reachability notifications on the current run loop 86 | - (BOOL) startNotifier; 87 | - (void) stopNotifier; 88 | 89 | - (NetworkStatus) currentReachabilityStatus; 90 | //WWAN may be available, but not active until a connection has been established. 91 | //WiFi may require a connection for VPN on Demand. 92 | - (BOOL) connectionRequired; 93 | @end 94 | 95 | 96 | -------------------------------------------------------------------------------- /ADBFeedReader/Reachability/Reachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.m 4 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 5 | 6 | Version: 2.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | 50 | #import "Reachability.h" 51 | 52 | #define kShouldPrintReachabilityFlags 1 53 | 54 | static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) 55 | { 56 | #if kShouldPrintReachabilityFlags 57 | 58 | NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", 59 | (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', 60 | (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', 61 | 62 | (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', 63 | (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', 64 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', 65 | (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', 66 | (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', 67 | (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', 68 | (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', 69 | comment 70 | ); 71 | #endif 72 | } 73 | 74 | 75 | @implementation Reachability 76 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) 77 | { 78 | #pragma unused (target, flags) 79 | NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); 80 | NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); 81 | 82 | //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively 83 | // in case someon uses the Reachablity object in a different thread. 84 | NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init]; 85 | 86 | Reachability* noteObject = (Reachability*) info; 87 | // Post a notification to notify the client that the network reachability changed. 88 | [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; 89 | 90 | [myPool release]; 91 | } 92 | 93 | - (BOOL) startNotifier 94 | { 95 | BOOL retVal = NO; 96 | SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; 97 | if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) 98 | { 99 | if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) 100 | { 101 | retVal = YES; 102 | } 103 | } 104 | return retVal; 105 | } 106 | 107 | - (void) stopNotifier 108 | { 109 | if(reachabilityRef!= NULL) 110 | { 111 | SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 112 | } 113 | } 114 | 115 | - (void) dealloc 116 | { 117 | [self stopNotifier]; 118 | if(reachabilityRef!= NULL) 119 | { 120 | CFRelease(reachabilityRef); 121 | } 122 | [super dealloc]; 123 | } 124 | 125 | + (Reachability*) reachabilityWithHostName: (NSString*) hostName; 126 | { 127 | Reachability* retVal = NULL; 128 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); 129 | if(reachability!= NULL) 130 | { 131 | retVal= [[[self alloc] init] autorelease]; 132 | if(retVal!= NULL) 133 | { 134 | retVal->reachabilityRef = reachability; 135 | retVal->localWiFiRef = NO; 136 | } 137 | } 138 | return retVal; 139 | } 140 | 141 | + (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; 142 | { 143 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); 144 | Reachability* retVal = NULL; 145 | if(reachability!= NULL) 146 | { 147 | retVal= [[[self alloc] init] autorelease]; 148 | if(retVal!= NULL) 149 | { 150 | retVal->reachabilityRef = reachability; 151 | retVal->localWiFiRef = NO; 152 | } 153 | } 154 | return retVal; 155 | } 156 | 157 | + (Reachability*) reachabilityForInternetConnection; 158 | { 159 | struct sockaddr_in zeroAddress; 160 | bzero(&zeroAddress, sizeof(zeroAddress)); 161 | zeroAddress.sin_len = sizeof(zeroAddress); 162 | zeroAddress.sin_family = AF_INET; 163 | return [self reachabilityWithAddress: &zeroAddress]; 164 | } 165 | 166 | + (Reachability*) reachabilityForLocalWiFi; 167 | { 168 | struct sockaddr_in localWifiAddress; 169 | bzero(&localWifiAddress, sizeof(localWifiAddress)); 170 | localWifiAddress.sin_len = sizeof(localWifiAddress); 171 | localWifiAddress.sin_family = AF_INET; 172 | // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 173 | localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 174 | Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress]; 175 | if(retVal!= NULL) 176 | { 177 | retVal->localWiFiRef = YES; 178 | } 179 | return retVal; 180 | } 181 | 182 | #pragma mark Network Flag Handling 183 | 184 | - (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags 185 | { 186 | PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); 187 | 188 | BOOL retVal = NotReachable; 189 | if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) 190 | { 191 | retVal = ReachableViaWiFi; 192 | } 193 | return retVal; 194 | } 195 | 196 | - (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags 197 | { 198 | PrintReachabilityFlags(flags, "networkStatusForFlags"); 199 | if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) 200 | { 201 | // if target host is not reachable 202 | return NotReachable; 203 | } 204 | 205 | BOOL retVal = NotReachable; 206 | 207 | if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) 208 | { 209 | // if target host is reachable and no connection is required 210 | // then we'll assume (for now) that your on Wi-Fi 211 | retVal = ReachableViaWiFi; 212 | } 213 | 214 | 215 | if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || 216 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) 217 | { 218 | // ... and the connection is on-demand (or on-traffic) if the 219 | // calling application is using the CFSocketStream or higher APIs 220 | 221 | if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) 222 | { 223 | // ... and no [user] intervention is needed 224 | retVal = ReachableViaWiFi; 225 | } 226 | } 227 | 228 | if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) 229 | { 230 | // ... but WWAN connections are OK if the calling application 231 | // is using the CFNetwork (CFSocketStream?) APIs. 232 | retVal = ReachableViaWWAN; 233 | } 234 | return retVal; 235 | } 236 | 237 | - (BOOL) connectionRequired; 238 | { 239 | NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); 240 | SCNetworkReachabilityFlags flags; 241 | if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) 242 | { 243 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired); 244 | } 245 | return NO; 246 | } 247 | 248 | - (NetworkStatus) currentReachabilityStatus 249 | { 250 | NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); 251 | NetworkStatus retVal = NotReachable; 252 | SCNetworkReachabilityFlags flags; 253 | if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) 254 | { 255 | if(localWiFiRef) 256 | { 257 | retVal = [self localWiFiStatusForFlags: flags]; 258 | } 259 | else 260 | { 261 | retVal = [self networkStatusForFlags: flags]; 262 | } 263 | } 264 | return retVal; 265 | } 266 | @end 267 | -------------------------------------------------------------------------------- /ADBFeedReader/UIViewController+CoreData.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+CoreData.h 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIViewController (CoreData) 12 | 13 | @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ADBFeedReader/UIViewController+CoreData.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+CoreData.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright (c) 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIViewController+CoreData.h" 11 | 12 | NSString const *kUIViewControllerManagedObjectContext = @"managedObjectContext"; 13 | 14 | @implementation UIViewController (CoreData) 15 | 16 | @dynamic managedObjectContext; 17 | 18 | #pragma mark - Accessors using runtime 19 | 20 | - (NSManagedObjectContext *)managedObjectContext 21 | { 22 | return objc_getAssociatedObject(self, (__bridge const void *)(kUIViewControllerManagedObjectContext)); 23 | } 24 | 25 | - (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext 26 | { 27 | [self willChangeValueForKey:@"managedObjectContext"]; 28 | objc_setAssociatedObject(self, (__bridge const void *)(kUIViewControllerManagedObjectContext), managedObjectContext, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 29 | [self didChangeValueForKey:@"managedObjectContext"]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ADBFeedReader/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | -------------------------------------------------------------------------------- /ADBFeedReader/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ADBFeedReader 4 | // 5 | // Created by Alberto De Bortoli on 20/05/2013. 6 | // Copyright 2013 Alberto De Bortoli. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "ADBAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([ADBAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ADBFeedReaderTest/ADBFeedReader-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.albertodebortoli.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ADBFeedReaderTest/ADBFeedReader-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'ADBFeedReader' target in the 'ADBFeedReader' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /ADBFeedReaderTest/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /FauxPasConfig/main.fauxpas.json: -------------------------------------------------------------------------------- 1 | // Generated from the Faux Pas GUI on 2014-08-01 23:13:57 +0000 2 | { 3 | // Xcode target to check (String) 4 | "target": "ADBFeedReader", 5 | // Xcode build configuration to check (String) 6 | "buildConfig": "Debug", 7 | // Rules to apply (Array of strings) 8 | //"rules": [], 9 | // Rules to apply only (Array of strings) 10 | //"onlyRules": [], 11 | // Rule tags to apply (Array of strings) 12 | //"ruleTags": ["Recommended"], 13 | // Rules to exclude (Array of strings) 14 | //"excludedRules": [], 15 | // Prefixes of files to exclude (Array of strings) 16 | //"fileExclusionPrefixes": [], 17 | // Xcode groups to exclude (Array of strings) 18 | //"fileExclusionXcodeGroups": ["Vendor"], 19 | // Regexes for files to exclude (Array of regular expression strings) 20 | //"fileExclusionRegexes": [], 21 | // Minimum diagnostic severity to return nonzero exit status ("Concern" / 22 | // "Warning" / "Error" / "Fatal") 23 | //"minErrorStatusSeverity": "Error", 24 | // Xcode workspace to build project with (File path) 25 | "workspace": "ADBFeedReader.xcworkspace", 26 | // Xcode scheme to build project with (String) 27 | "scheme": "ADBFeedReader", 28 | // Number of concurrent file checking jobs (Positive integer) 29 | //"numConcurrentJobs": null, 30 | // Additional compiler arguments to use (Array of strings) 31 | //"extraCompilerArgs": [], 32 | // Additional xcodebuild arguments to use (Array of strings) 33 | //"extraXcodebuildArgs": [], 34 | // Build project before checking (Boolean) 35 | //"fullBuild": false, 36 | // Cache project build logs (Boolean) 37 | //"cacheBuildLog": true, 38 | // Use our own modules cache (Boolean) 39 | //"useOwnModulesCache": true, 40 | // Verbose output (Boolean) 41 | //"verbose": false, 42 | // Output format ("human" / "json" / "plist" / "xml" / "xcode") 43 | //"outputFormat": "human", 44 | 45 | // Options for rules: 46 | "ruleOptions": { 47 | // Options for localization rules 48 | "_LocalizationRules": { 49 | // Functions used in place of NSLocalizedString (Array of strings) 50 | //"localizedStringRoutines": [] 51 | }, 52 | // Options for rule: Absolute path in build setting value 53 | "AbsPathInBuildSetting": { 54 | // Allow system paths (Boolean) 55 | //"allowSystemPaths": false 56 | }, 57 | // Options for rule: Block-typed declaration without typedef 58 | "NonTypedefBlockDeclaration": { 59 | // Apply only to function/method arguments (Boolean) 60 | //"onlyArguments": true 61 | }, 62 | // Options for rule: Build settings set in Xcode GUI 63 | "BuildSettingsSetInGUI": { 64 | // Which settings are allowed to be set in the GUI (Array of 65 | // strings) 66 | //"allowedGUISettings": ["CODE_SIGN_IDENTITY","PROVISIONING_PROFILE"] 67 | }, 68 | // Options for rule: Constructor return type 69 | "ConstructorReturnType": { 70 | // Check init methods (Boolean) 71 | //"checkInitMethods": false 72 | }, 73 | // Options for rule: Discarded opaque NSNotificationCenter observer 74 | "DiscardedOpaqueNotificationObserver": { 75 | // Do not warn if explicitly cast to void (Boolean) 76 | //"suppressViaVoidCast": true 77 | }, 78 | // Options for rule: Dot syntax usage 79 | "DotSyntax": { 80 | // Enforce dot syntax only for properties (Boolean) 81 | //"onlyForProperties": false, 82 | // Square bracket syntax method whitelist (Array of strings) 83 | "allowedMethods": ["alloc","autorelease","becomeFirstResponder","new","release","retain","runModal","setNeedsDisplay:"] 84 | // Allow factory methods like +[NSArray 85 | // array] (Boolean) 86 | //"allowConstructors": true 87 | }, 88 | // Options for rule: Globally caching a thread-unsafe class instance 89 | "ThreadUnsafeInstanceCaching": { 90 | // The thread-unsafe classes to warn about (Array of strings) 91 | "classNames": ["NSDateFormatter","NSMutableArray","NSMutableAttributedString","NSMutableCharacterSet","NSMutableData","NSMutableDictionary","NSMutableSet","NSMutableString","NSNumberFormatter"] 92 | }, 93 | // Options for rule: Identifier naming 94 | "IdentifierNaming": { 95 | // Instance variable name format regex (Regular expression) 96 | //"ivarNameFormat": "^_.+" 97 | // Local variable name format regex (Regular expression) 98 | //"localVarNameFormat": "^[^_].*" 99 | // Function name format regex (Regular expression) 100 | //"functionNameFormat": null 101 | // Typedef name format regex (Regular expression) 102 | //"typedefNameFormat": null 103 | // Macro name format regex (Regular expression) 104 | //"macroNameFormat": null 105 | // Objective-C method name format regex (Regular expression) 106 | //"objcMethodNameFormat": null 107 | // Objective-C class name format regex (Regular expression) 108 | //"objcClassNameFormat": null 109 | // Objective-C category name format regex (Regular expression) 110 | //"objcCategoryNameFormat": null 111 | }, 112 | // Options for rule: Macro definition for literal value 113 | "MacroLiteral": { 114 | // Allowed literal values (Array of strings) 115 | //"allowedLiterals": ["0","1"] 116 | }, 117 | // Options for rule: Missing translation 118 | "MissingTranslation": { 119 | // Warn about missing development region translations (Boolean) 120 | //"checkDevRegion": false 121 | // Ignored key prefixes (Array of strings) 122 | //"ignoredKeyPrefixes": [] 123 | }, 124 | // Options for rule: Moving common inclusions into prefix header 125 | "PrefixHeaderIncludeSuggestion": { 126 | // Show including files (Boolean) 127 | //"showFiles": false 128 | // Warning threshold percentage (Positive integer) 129 | //"warningThresholdPercent": 30 130 | }, 131 | // Options for rule: NSLog() used in release build 132 | "NSLogUsed": { 133 | // Allow calling NSLog() via macros (Boolean) 134 | //"allowMacros": true 135 | }, 136 | // Options for rule: Problematic release build compiler arguments 137 | "ReleaseBuildCompilerArgs": { 138 | // Required compiler arguments for all files (Array of strings) 139 | "requiredArguments": ["-DNDEBUG","-DNS_BLOCK_ASSERTIONS=1"] 140 | // Disallowed compiler arguments for all files (Array of strings) 141 | //"disallowedArguments": [] 142 | }, 143 | // Options for rule: Project reference to file using absolute path 144 | "FileRefWithAbsPath": { 145 | // Allow system paths (Boolean) 146 | //"allowSystemPaths": false 147 | }, 148 | // Options for rule: Recommended VCS ignores 149 | "RecommendedVCSIgnores": { 150 | // Whether the Xcode workspace data should be ignored (Boolean) 151 | //"workspaceIgnored": false 152 | // Whether CocoaPods data should be ignored (Boolean) 153 | //"cocoaPodsIgnored": true 154 | }, 155 | // Options for rule: Recommended compiler warning options 156 | "CompilerWarnings": { 157 | // Recommended warning flags (Array of strings) 158 | "flags": ["-Wall","-Wconversion","-Wdeprecated-implementations","-Wempty-body","-Werror","-Wextra","-Wfloat-equal","-Wimplicit-retain-self","-Wnewline-eof","-Wreturn-type","-Wshadow","-Wsign-compare","-Wundef"] 159 | }, 160 | // Options for rule: Recommended project settings 161 | "BasicProjectSettings": { 162 | // The indentation type that should be set ("tabs" / "spaces") 163 | //"indentType": null 164 | // The 'tab width' that should be set (Positive integer) 165 | //"tabWidth": null 166 | // The 'indent width' that should be set (Positive integer) 167 | //"indentWidth": null 168 | // Whether the 'wrap lines' setting should be set or not ("wrap" / 169 | // "nowrap") 170 | //"wrapLines": null 171 | }, 172 | // Options for rule: Redundant inclusion directive 173 | "RedundantInclude": { 174 | // Ignore system headers (Boolean) 175 | //"ignoreSystemHeaders": true 176 | // Ignore indirect inclusions (Boolean) 177 | //"ignoreIndirectInclusions": false 178 | }, 179 | // Options for rule: Reserved identifier name 180 | "ReservedIdentifierNaming": { 181 | // Check for identifier names reserved by the C standard (Boolean) 182 | //"checkCStandard": true 183 | // Check for identifier names reserved by the POSIX standard 184 | // (Boolean) 185 | //"checkPOSIXStandard": true 186 | }, 187 | // Options for rule: Reserved symbol prefix 188 | "ReservedPrefix": { 189 | // Allow two-character prefixes that don't overlap system 190 | // frameworks (Boolean) 191 | //"allowNonOverlappingTwoCharPrefix": false 192 | }, 193 | // Options for rule: Restricted direct method call 194 | "RestrictedDirectMethodCall": { 195 | // Allow direct calls from subclasses (Boolean) 196 | //"allowInSubclass": true 197 | }, 198 | // Options for rule: Setter invocation in init or dealloc method 199 | "SetterInvocationInInitOrDealloc": { 200 | // Apply to init methods (Boolean) 201 | //"applyToInit": true 202 | // Apply to dealloc methods (Boolean) 203 | //"applyToDealloc": true 204 | // Warn only if ivar available (Boolean) 205 | //"onlyIfIvarAvailable": true 206 | }, 207 | // Options for rule: Shortcut initializer 208 | "NewInitializer": { 209 | // Prefer the alloc-init style (Boolean) 210 | //"preferAllocInit": true 211 | }, 212 | // Options for rule: Translation border punctuation mismatch 213 | "TranslationPunctuation": { 214 | // Punctuation characters that should match (String) 215 | //"punctuationChars": ".,:;!?…。" 216 | }, 217 | // Options for rule: Unprefixed category method 218 | "UnprefixedCategoryMethod": { 219 | // Allowed prefixes (Array of strings) 220 | //"prefixes": null 221 | // Ignore setters (Boolean) 222 | //"ignoreSetters": true 223 | }, 224 | // Options for rule: Use of API not available in the minimum deployment 225 | // target 226 | "APIAvailability": { 227 | // Avoid false positives (Boolean) 228 | //"avoidFalsePositives": false 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /LICENSE.markdown: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Alberto De Bortoli 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Alberto De Bortoli nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL Alberto De Bortoli BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, 6.0 2 | pod 'MBProgressHUD', '~>0.6' 3 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MBProgressHUD (0.8) 3 | 4 | DEPENDENCIES: 5 | - MBProgressHUD (~> 0.6) 6 | 7 | SPEC CHECKSUMS: 8 | MBProgressHUD: 2bbc6f470111daf7f3eaa4eb12b8cbf01c4c0622 9 | 10 | COCOAPODS: 0.30.0 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADB Feed Reader 2 | ### Author: Alberto De Bortoli 3 | 4 | 5 | ## Summary 6 | 7 | A simple and clean RSS Feed Reader for iOS. I'm releasing this project so you can study it and learn how to make apps (or how *not* to make them, depending on what you think of my code). [citation needed][1] 8 | 9 | The app has a simple master-detail navigation flow. The feed reader uses *NSXMLParser* with a SAX-style state machine fashion. 10 | 11 | Even if the HTTP request to the XML feed is asynchronous (using *NSURLConnection* delegates), the table view is reloaded when all data has been parsed. It would be possibile to handle tableview updates smartly: for example the controller could handle table view insertions as soon as the delegate of the feed reader is notified that a new feed item has been parsed. 12 | 13 | ## Project 14 | 15 | I did not use any important 3rd party library for the main parts of the task (the HTTP request, the XML parsing and the use of Core Data…). 16 | 17 | I used Apple *Reachability* class a *NSDate* category I used in my personal projeccts to conveniently format dates. 18 | 19 | As an extra, I used also my own *ADBWebBrowserViewController* to load a web page inside the demo app (by pressing the link cell in the detail view). 20 | 21 | I used 2 minor external libraries via CocoaPods just for improving UX: 22 | 23 | * *MBProgressHUD* (needed by *ADBWebBrowserViewController*) 24 | * *SVPullToRefresh* 25 | 26 | 27 | ## Instructions to use as a third-party component 28 | 29 | The main part of the project (the feed parser *ADBFeedParser*) can be easily used as a drop-in component. To use it as a third-party component (refer to `ADBFeedParser.h`) just follow these steps: 30 | 31 | * Copy `ADBFeedParser.{h|m}` in your project 32 | * Use the following code to start a parsing against a URL: 33 | 34 | ``` 35 | ADBFeedParser *feedParser = [[ADBFeedParser alloc] initWithURL:aFeedURL]; 36 | feedParser.delegate = self; 37 | [feedParser start]; 38 | ``` 39 | 40 | * Make your controller conform * to *ADBFeedReaderDelegate* protocol and implement the methods you need for retrieving informations from the feed (the methods are all optional): 41 | 42 | ``` 43 | - (void)feedParserDidStart:(ADBFeedParser *)parser; 44 | - (void)feedParser:(ADBFeedParser *)parser didParseFeedInfo:(FeedInfo *)info; 45 | - (void)feedParser:(ADBFeedParser *)parser didParseFeedItem:(FeedItem *)item; 46 | - (void)feedParserDidFinish:(ADBFeedParser *)parser; 47 | - (void)feedParser:(ADBFeedParser *)parser didFailWithError:(NSError *)error; 48 | ``` 49 | 50 | ## Tests with CLANG and Instruments 51 | 52 | ADBFeedReader builds with no warnings ('Treat Warnings as Errors' enabled) and passes CLANG static analysis with no warnings. Some checks for allocations and leaks with Instruments have been made with success (see attachment image showing the persistent objects between scrolls). 53 | 54 | ![Analysis](./analysis.png) 55 | 56 | ## License 57 | 58 | Copyright (c) 2013, Alberto De Bortoli 59 | All rights reserved. 60 | 61 | Redistribution and use in source and binary forms, with or without 62 | modification, are permitted provided that the following conditions are met: 63 | * Redistributions of source code must retain the above copyright 64 | notice, this list of conditions and the following disclaimer. 65 | * Redistributions in binary form must reproduce the above copyright 66 | notice, this list of conditions and the following disclaimer in the 67 | documentation and/or other materials provided with the distribution. 68 | * Neither the name of Alberto De Bortoli nor the 69 | names of its contributors may be used to endorse or promote products 70 | derived from this software without specific prior written permission. 71 | 72 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 73 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 74 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 75 | DISCLAIMED. IN NO EVENT SHALL Alberto De Bortoli BE LIABLE FOR ANY 76 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 77 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 78 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 79 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 80 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 81 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 82 | 83 | 84 | [1]: https://github.com/nicklockwood/Concurrency/blob/master/README.md 85 | -------------------------------------------------------------------------------- /analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinHM/ADBFeedReader/4fa6e394805618261b7efe27746cbf3065cb56e3/analysis.png --------------------------------------------------------------------------------