├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── Tests ├── RunTests.sh ├── Tests-Info.plist └── main.m ├── V2EX.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Tests.xcscheme ├── V2EX.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── V2EX.xccheckout └── V2EX ├── Base.lproj └── Main.storyboard ├── Controllers ├── UIViewController+MBProgressHUD.h ├── UIViewController+MBProgressHUD.m ├── UIViewController+Size.h ├── UIViewController+Size.m ├── UIViewController+V2EXJump.h ├── UIViewController+V2EXJump.m ├── V2EXAfterTopicActionDelegate.h ├── V2EXLatestTopicsViewController.h ├── V2EXLatestTopicsViewController.m ├── V2EXMenuViewController.h ├── V2EXMenuViewController.m ├── V2EXMyRepliesViewController.h ├── V2EXMyRepliesViewController.m ├── V2EXMyTopicsViewController.h ├── V2EXMyTopicsViewController.m ├── V2EXNewTopicViewController.h ├── V2EXNewTopicViewController.m ├── V2EXNodesListViewController.h ├── V2EXNodesListViewController.m ├── V2EXNotificationCenterViewController.h ├── V2EXNotificationCenterViewController.m ├── V2EXReplyTopicViewController.h ├── V2EXReplyTopicViewController.m ├── V2EXRootViewController.h ├── V2EXRootViewController.m ├── V2EXSelfViewController.h ├── V2EXSelfViewController.m ├── V2EXSettingsViewController.h ├── V2EXSettingsViewController.m ├── V2EXSingleTopicViewController.h ├── V2EXSingleTopicViewController.m ├── V2EXTableViewController.h ├── V2EXTableViewController.m ├── V2EXTopicsListInSingleNodeViewController.h ├── V2EXTopicsListInSingleNodeViewController.m ├── V2EXUserLoginViewController.h └── V2EXUserLoginViewController.m ├── Images.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-40.png │ ├── Icon-40@2x-1.png │ ├── Icon-40@2x.png │ ├── Icon-60@2x.png │ ├── Icon-72.png │ ├── Icon-72@2x.png │ ├── Icon-76.png │ ├── Icon-76@2x.png │ ├── Icon-Small-1.png │ ├── Icon-Small-50.png │ ├── Icon-Small-50@2x.png │ ├── Icon-Small.png │ ├── Icon-Small@2x-1.png │ ├── Icon-Small@2x.png │ ├── Icon.png │ └── Icon@2x.png ├── IconBack.imageset │ ├── Contents.json │ └── IconBack@2x.png ├── IconChatting.imageset │ ├── Contents.json │ └── chatting.png ├── IconEmpty.imageset │ ├── Contents.json │ └── IconEmpty@2x.png ├── IconHome.imageset │ ├── Contents.json │ └── IconHome@2x.png ├── IconKey.imageset │ ├── Contents.json │ └── IconKey@2x.png ├── IconMenu.imageset │ ├── Contents.json │ ├── icon_menu.png │ └── icon_menu@2x.png ├── IconProfile.imageset │ ├── Contents.json │ └── IconProfile@2x.png ├── IconRecent.imageset │ ├── Contents.json │ ├── IconRecent@2x.png │ └── timer-vector.psd ├── IconReply.imageset │ ├── Contents.json │ └── speech-bubble-vector.png ├── IconSettings.imageset │ ├── Contents.json │ └── IconSettings@2x.png ├── LaunchImage.launchimage │ ├── Contents.json │ ├── Default@1536x2048-1.png │ ├── Default@1536x2048.png │ ├── Default@640x1136-1.png │ ├── Default@640x960.png │ ├── Default@768x1024-1.png │ └── Default@768x1024.png ├── LaunchImage2.launchimage │ ├── Contents.json │ ├── Default@1024x748.png │ ├── Default@1024x768-1.png │ ├── Default@1024x768.png │ ├── Default@1536x2008.png │ ├── Default@1536x2048-1.png │ ├── Default@1536x2048.png │ ├── Default@2048x1496.png │ ├── Default@2048x1536-1.png │ ├── Default@2048x1536.png │ ├── Default@320x480.png │ ├── Default@640x1136-1.png │ ├── Default@640x1136.png │ ├── Default@640x960-1.png │ ├── Default@640x960.png │ ├── Default@768x1004.png │ ├── Default@768x1024-1.png │ └── Default@768x1024.png ├── Stars.imageset │ ├── Contents.json │ └── Stars@2x.png └── avatar_large.imageset │ ├── Contents.json │ └── avatar_large.png ├── Models ├── Data │ ├── NodesList │ │ ├── NodesAll.json │ │ └── NodesList.plist │ └── repliesList.css ├── V2EXAbstractModel.h ├── V2EXAbstractModel.m ├── V2EXNodesListModel.h ├── V2EXNodesListModel.m ├── V2EXNormalModel.h ├── V2EXNormalModel.m └── V2EXRequestDataDelegate.h ├── ThirdParties └── DRPaginatedScrollView │ ├── DRPaginatedScrollView.h │ └── DRPaginatedScrollView.m ├── Utilities ├── Network │ ├── JSONAPIURI.h │ ├── NormalAPIURI.h │ ├── V2EXApiClient.h │ ├── V2EXApiClient.m │ ├── V2EXHttpRequestSerializer.h │ └── V2EXHttpRequestSerializer.m ├── TFHpple+V2EXMethod.h ├── TFHpple+V2EXMethod.m ├── TFHppleElement+V2EXMethod.h ├── TFHppleElement+V2EXMethod.m ├── UI │ ├── UIView+FrameMethods.h │ ├── UIView+FrameMethods.m │ ├── V2EXAppSize.h │ ├── V2EXAppSize.m │ ├── V2EXMBProgressHUDUtil.h │ └── V2EXMBProgressHUDUtil.m ├── V2EXGlobalCache.h ├── V2EXGlobalCache.m ├── V2EXStringUtil.h └── V2EXStringUtil.m ├── V2EX-Info.plist ├── V2EX-Prefix.pch ├── V2EXAppDelegate.h ├── V2EXAppDelegate.m ├── Views ├── Categories │ ├── UIView+FrameMethods.h │ └── UIView+FrameMethods.m └── TableViewCells │ ├── V2EXNodesListCell.h │ ├── V2EXNodesListCell.m │ ├── V2EXNodesListCell.xib │ ├── V2EXTopicsListCell.h │ ├── V2EXTopicsListCell.m │ ├── V2EXTopicsListCell.xib │ ├── V2EXUserTopicsListCell.h │ ├── V2EXUserTopicsListCell.m │ └── V2EXUserTopicsListCell.xib ├── en.lproj └── InfoPlist.strings └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods 24 | 25 | #GHUnit 26 | Tests 27 | Tests/MyTest.m 28 | Tests/MyTest.m 29 | 30 | Tests/MyTest.m 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) < 2014 > < WildCat > 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '7.0' 2 | pod 'AFNetworking', '~> 2.1.0' 3 | pod 'RESideMenu', '~> 3.4' 4 | pod 'Hpple', :git => 'https://github.com/topfunky/hpple.git' 5 | pod 'MBProgressHUD' 6 | pod 'SVPullToRefresh' 7 | pod 'SDWebImage' 8 | pod 'HMSegmentedControl', '~> 1.3.0' 9 | pod 'Masonry' 10 | pod 'FMDB' 11 | pod 'DDPageControl' 12 | pod 'DTCoreText' 13 | #pod 'MultiLayerNavigation', :git => 'https://github.com/myoula/MultiLayerNavigation.git' 14 | 15 | #JLRubyChina 16 | #pod 'Nimbus', '~> 1.0.0' 17 | #pod 'MTStatusBarOverlay' 18 | #pod 'PPRevealSideViewController' 19 | #pod 'SSKeychain' 20 | #pod 'HPGrowingTextView' 21 | #pod 'SDSegmentedControl' 22 | #pod 'NSStringEmojize' 23 | #pod 'LTUpdate', '~> 0.0.2' 24 | #pod 'RTagCloudView' 25 | #pod 'NSAttributedStringMarkdownParser', '~> 0.0.1' 26 | 27 | target :Tests do 28 | pod 'GHUnit' 29 | end 30 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (2.1.0): 3 | - AFNetworking/NSURLConnection 4 | - AFNetworking/NSURLSession 5 | - AFNetworking/Reachability 6 | - AFNetworking/Security 7 | - AFNetworking/Serialization 8 | - AFNetworking/UIKit 9 | - AFNetworking/NSURLConnection (2.1.0): 10 | - AFNetworking/Reachability 11 | - AFNetworking/Security 12 | - AFNetworking/Serialization 13 | - AFNetworking/NSURLSession (2.1.0): 14 | - AFNetworking/NSURLConnection 15 | - AFNetworking/Reachability (2.1.0) 16 | - AFNetworking/Security (2.1.0) 17 | - AFNetworking/Serialization (2.1.0) 18 | - AFNetworking/UIKit (2.1.0): 19 | - AFNetworking/NSURLConnection 20 | - DDPageControl (0.1) 21 | - DTCoreText (1.6.11): 22 | - DTFoundation/Core (~> 1.6.1) 23 | - DTFoundation/DTHTMLParser (~> 1.6.1) 24 | - DTFoundation/UIKit (~> 1.6.1) 25 | - DTFoundation/Core (1.6.2) 26 | - DTFoundation/DTHTMLParser (1.6.2): 27 | - DTFoundation/Core 28 | - DTFoundation/UIKit (1.6.2): 29 | - DTFoundation/Core 30 | - FMDB (2.2): 31 | - FMDB/standard 32 | - FMDB/common (2.2) 33 | - FMDB/standard (2.2): 34 | - FMDB/common 35 | - GHUnit (0.5.9) 36 | - HMSegmentedControl (1.3.0) 37 | - Hpple (0.2.0) 38 | - Masonry (0.4.0) 39 | - MBProgressHUD (0.8) 40 | - RESideMenu (3.4) 41 | - SDWebImage (3.6): 42 | - SDWebImage/Core 43 | - SDWebImage/Core (3.6) 44 | - SVPullToRefresh (0.4.1) 45 | 46 | DEPENDENCIES: 47 | - AFNetworking (~> 2.1.0) 48 | - DDPageControl 49 | - DTCoreText 50 | - FMDB 51 | - GHUnit 52 | - HMSegmentedControl (~> 1.3.0) 53 | - Hpple (from `https://github.com/topfunky/hpple.git`) 54 | - Masonry 55 | - MBProgressHUD 56 | - RESideMenu (~> 3.4) 57 | - SDWebImage 58 | - SVPullToRefresh 59 | 60 | EXTERNAL SOURCES: 61 | Hpple: 62 | :git: https://github.com/topfunky/hpple.git 63 | 64 | SPEC CHECKSUMS: 65 | AFNetworking: c7d7901a83f631414c7eda1737261f696101a5cd 66 | DDPageControl: ed243202fe4e210f4f2c6e7de26e3c06260c6cd6 67 | DTCoreText: 4d56bb74ff05a3b2415fc15fccc4b5689ed3119c 68 | DTFoundation: 7c69990fa3cb7655cafa95ab59c3fcea014460f0 69 | FMDB: e0dd09464b4e1a1cd7fa51ea835ae821b5656865 70 | GHUnit: 2f3ea0192633e92afaef959476a8d28db179797d 71 | HMSegmentedControl: 3a58d4fc011e40c93c5299b9e8283e89c32f4c64 72 | Hpple: 75ce7943178ebff8812698cb11ade69dea9db141 73 | Masonry: d673617c9cbc24aaea90fba20b10e47fd9970a39 74 | MBProgressHUD: 2bbc6f470111daf7f3eaa4eb12b8cbf01c4c0622 75 | RESideMenu: d8d6330052e0348718826045b912a1c71748357d 76 | SDWebImage: 7eb273e54ca8a2e506ad9bd5e80c35b2a4d7c95e 77 | SVPullToRefresh: 61a0e4bd12bd6f8e3465909810b0fbeb1a28d5f2 78 | 79 | COCOAPODS: 0.32.1 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | V2EX-iOS 2 | ======== 3 | The iOS client for V2EX. 4 | 5 | How to start 6 | ======== 7 | This project is using CocoaPods. To get more information about it, please go to the following link: 8 | http://code4app.com/article/cocoapods-install-usage 9 | 10 | **And if you are living inside the Great Fire Wall, you might have to use a mirror of CocoaPods Specs, see about this:** 11 | http://www.v2ex.com/t/110435 12 | 13 | TODO 14 | ======== 15 | 1. Display images in topics(https://github.com/imWildCat/V2EX-iOS/issues/5) 16 | 2. Fix request time out error in Latest Topics Controller (https://github.com/imWildCat/V2EX-iOS/issues/3) 17 | 3. Add support for "My favourite Nodes" & "My favourite Topics" 18 | 4. Fix line break bug in NewTopicController & ReplyTopicController(https://github.com/imWildCat/V2EX-iOS/issues/4) 19 | 5. Remove singleton of V2EXApiClient 20 | 21 | Author's message 22 | ======== 23 | I'm a beginner of programming. As a result, there are many shortcomings of this project. If you find them, please tell me. 24 | In addition, I'm also a English learners who attempt to write code comments in English. There might be a lot of communication confusions. Please spur on me. 25 | 26 | By the way, if you'd like to use Chinese. Just do it. And you can ask me to translate some of my poor English into Chinese XD. 27 | 顺便说下,如果您希望使用汉语,那就用吧。您也可以要求我把一些说得不好的英文翻译为中文。 28 | 29 | Thank you very much! 30 | 31 | LICENSE 32 | ======== 33 | The MIT License. 34 | -------------------------------------------------------------------------------- /Tests/RunTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If we aren't running from the command line, then exit 4 | if [ "$GHUNIT_CLI" = "" ] && [ "$GHUNIT_AUTORUN" = "" ]; then 5 | exit 0 6 | fi 7 | 8 | TEST_TARGET_EXECUTABLE_PATH="$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH" 9 | 10 | if [ ! -e "$TEST_TARGET_EXECUTABLE_PATH" ]; then 11 | echo "" 12 | echo " ------------------------------------------------------------------------" 13 | echo " Missing executable path: " 14 | echo " $TEST_TARGET_EXECUTABLE_PATH." 15 | echo " The product may have failed to build." 16 | echo " ------------------------------------------------------------------------" 17 | echo "" 18 | exit 1 19 | fi 20 | 21 | RUN_CMD="ios-sim launch \"$TEST_TARGET_EXECUTABLE_PATH\"" 22 | 23 | echo "Running: $RUN_CMD" 24 | set +o errexit # Disable exiting on error so script continues if tests fail 25 | eval $RUN_CMD 26 | RETVAL=$? 27 | set -o errexit 28 | 29 | if [ -n "$WRITE_JUNIT_XML" ]; then 30 | MY_TMPDIR=`/usr/bin/getconf DARWIN_USER_TEMP_DIR` 31 | RESULTS_DIR="${MY_TMPDIR}test-results" 32 | 33 | if [ -d "$RESULTS_DIR" ]; then 34 | `$CP -r "$RESULTS_DIR" "$BUILD_DIR" && rm -r "$RESULTS_DIR"` 35 | fi 36 | fi 37 | 38 | exit $RETVAL 39 | -------------------------------------------------------------------------------- /Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | ${PRODUCT_NAME} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | tests.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1.0 21 | LSRequiresIPhoneOS 22 | 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tests/main.m: -------------------------------------------------------------------------------- 1 | // This file auto-generated by ghunit gem 2 | #import 3 | 4 | int main(int argc, char *argv[]) { 5 | @autoreleasepool { 6 | return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /V2EX.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /V2EX.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 35 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 60 | 61 | 62 | 63 | 65 | 66 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /V2EX.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /V2EX.xcworkspace/xcshareddata/V2EX.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | D76D0B0A-B2E3-4A20-9499-1EA60F5DCFD9 9 | IDESourceControlProjectName 10 | V2EX 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 31FB247D-598F-41B1-A4F5-E75D1F035604 14 | file:///Users/WildCat/Library/Caches/CocoaPods/GitHub/b950e0b31a1d99b68df85d2c63a4ad3e774cc0c5/ 15 | CF0B353A-D8A3-4BCD-B987-321443E300F4 16 | https://github.com/imWildCat/V2EX-iOS.git 17 | 18 | IDESourceControlProjectPath 19 | V2EX.xcworkspace 20 | IDESourceControlProjectRelativeInstallPathDictionary 21 | 22 | 31FB247D-598F-41B1-A4F5-E75D1F035604 23 | ../Pods/hpple 24 | CF0B353A-D8A3-4BCD-B987-321443E300F4 25 | .. 26 | 27 | IDESourceControlProjectURL 28 | https://github.com/imWildCat/V2EX-iOS.git 29 | IDESourceControlProjectVersion 30 | 110 31 | IDESourceControlProjectWCCIdentifier 32 | CF0B353A-D8A3-4BCD-B987-321443E300F4 33 | IDESourceControlProjectWCConfigurations 34 | 35 | 36 | IDESourceControlRepositoryExtensionIdentifierKey 37 | public.vcs.git 38 | IDESourceControlWCCIdentifierKey 39 | 31FB247D-598F-41B1-A4F5-E75D1F035604 40 | IDESourceControlWCCName 41 | hpple 42 | 43 | 44 | IDESourceControlRepositoryExtensionIdentifierKey 45 | public.vcs.git 46 | IDESourceControlWCCIdentifierKey 47 | CF0B353A-D8A3-4BCD-B987-321443E300F4 48 | IDESourceControlWCCName 49 | V2EX-iOS 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+MBProgressHUD.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/5/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIViewController (MBProgressHUD) 12 | 13 | - (void) showProgressView; 14 | - (void) hideProgressView; 15 | - (void) showMessage:(NSString *)message; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+MBProgressHUD.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/5/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+MBProgressHUD.h" 10 | #import "V2EXMBProgressHUDUtil.h" 11 | 12 | @implementation UIViewController (MBProgressHUD) 13 | 14 | -(void)showProgressView { 15 | [V2EXMBProgressHUDUtil showGlobalProgressHUDWithTitle:nil]; 16 | } 17 | 18 | -(void)hideProgressView { 19 | [V2EXMBProgressHUDUtil dismissGlobalHUD]; 20 | } 21 | 22 | -(void)showMessage:(NSString *)message { 23 | [V2EXMBProgressHUDUtil showMessage:message]; 24 | } 25 | @end 26 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+Size.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Size.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/9/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIViewController (Size) 12 | 13 | - (CGFloat)getHeight; 14 | - (CGFloat)getWidth; 15 | - (CGFloat)getStatusBarHeight; 16 | - (CGFloat)getNavBarHeight; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+Size.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Size.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/9/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+Size.h" 10 | #import "V2EXAppSize.h" 11 | 12 | @implementation UIViewController (Size) 13 | 14 | - (CGFloat)getHeight { 15 | return [V2EXAppSize getHeightWithController:self]; 16 | } 17 | 18 | - (CGFloat)getWidth { 19 | return [V2EXAppSize getWidthWithController:self]; 20 | } 21 | 22 | - (CGFloat)getStatusBarHeight { 23 | return [V2EXAppSize getStatusBarHeight]; 24 | } 25 | 26 | - (CGFloat)getNavBarHeight { 27 | return [V2EXAppSize getNavBarHeight4Controller:self]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+V2EXJump.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+V2EXJump.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/3/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "V2EXTableViewController.h" 11 | 12 | @interface V2EXTableViewController (V2EXJump) 13 | 14 | - (void)pushToUserLoginController; 15 | - (void)pushToUserLoginController:(BOOL)isLoadFromSeflController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Controllers/UIViewController+V2EXJump.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+V2EXJump.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/3/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+V2EXJump.h" 10 | #import "V2EXUserLoginViewController.h" 11 | 12 | @implementation UIViewController (V2EXJump) 13 | 14 | - (void)pushToUserLoginController { 15 | [self pushToUserLoginController:NO]; 16 | } 17 | 18 | - (void)pushToUserLoginController:(BOOL)isLoadFromSeflController { 19 | V2EXUserLoginViewController *userLoginController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"userLoginController"]; 20 | userLoginController.isLoadFromSeflController = isLoadFromSeflController; 21 | [self.navigationController pushViewController:userLoginController animated:YES]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXAfterTopicActionDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAfterTopicActionDelegate 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/6/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol V2EXAfterTopicActionDelegate 12 | 13 | @optional 14 | 15 | - (void)afterCreateTopic:(NSData *)data; 16 | - (void)afterReplyTopic:(NSData *)data; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXLatestTopicsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXLatestTopicsViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "UIViewController+MBProgressHUD.h" 12 | #import "V2EXTableViewController.h" 13 | #import "V2EXNormalModel.h" 14 | 15 | @interface V2EXLatestTopicsViewController : V2EXTableViewController 16 | 17 | + (V2EXLatestTopicsViewController *)sharedController; 18 | - (IBAction)showMenu:(id)sender; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXLatestTopicsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXLatestTopicsViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXLatestTopicsViewController.h" 10 | #import "V2EXTopicsListCell.h" 11 | #import "V2EXStringUtil.h" 12 | 13 | @interface V2EXLatestTopicsViewController () 14 | 15 | @end 16 | 17 | @implementation V2EXLatestTopicsViewController 18 | 19 | + (V2EXLatestTopicsViewController *)sharedController 20 | { 21 | static V2EXLatestTopicsViewController *_sharedLatestTopicsViewControllerInstance = nil; 22 | static dispatch_once_t predicate; 23 | dispatch_once(&predicate, ^{ 24 | _sharedLatestTopicsViewControllerInstance = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"latestTopicsController"]; 25 | }); 26 | 27 | return _sharedLatestTopicsViewControllerInstance; 28 | } 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | 34 | // Uncomment the following line to preserve selection between presentations. 35 | // self.clearsSelectionOnViewWillAppear = NO; 36 | 37 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller. 38 | // self.navigationItem.rightBarButtonItem = self.editButtonItem; 39 | 40 | [self showProgressView]; 41 | [self loadData]; 42 | } 43 | 44 | - (void)didReceiveMemoryWarning 45 | { 46 | [super didReceiveMemoryWarning]; 47 | // Dispose of any resources that can be recreated. 48 | } 49 | 50 | - (void)loadData { 51 | if ([self canStartNewLoading]) { 52 | _loadingStatus = 1; 53 | [self loadListData]; 54 | } 55 | } 56 | 57 | - (void)loadListData { 58 | [self.model getLatestTopics]; 59 | } 60 | 61 | - (void)loadTopicData:(NSUInteger)ID { 62 | if ([self canStartNewLoading]) { 63 | _loadingStatus = 2; 64 | [self.model getTopicWithID:ID]; 65 | [self showProgressView]; 66 | } 67 | } 68 | 69 | 70 | #pragma mark - Table view data source 71 | 72 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 73 | { 74 | static NSString *CellIdentifier = @"topicsListCell"; 75 | 76 | // static BOOL nibsRegistered = NO; 77 | // if (!nibsRegistered) { 78 | // UINib *nib = [UINib nibWithNibName:@"V2EXTopicsListCell" bundle:nil]; 79 | // [tableView registerNib:nib forCellReuseIdentifier:CellIdentifier]; 80 | // nibsRegistered = YES; 81 | // } 82 | UINib *nib = [UINib nibWithNibName:@"V2EXTopicsListCell" bundle:nil]; 83 | [tableView registerNib:nib forCellReuseIdentifier:CellIdentifier]; 84 | 85 | V2EXTopicsListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 86 | 87 | // [cell.replies.layer setCornerRadius:8]; 88 | // [cell.userAvatar.layer setCornerRadius:cell.userAvatar.frame.size.width/5]; 89 | //// cell.userAvatar.layer.masksToBounds = YES; 90 | 91 | NSUInteger row = [indexPath row]; 92 | id rowData = [self.data objectAtIndex:row]; 93 | [cell.title setText:[rowData valueForKey:@"title"]]; 94 | [cell.nodeTitle setText:[[rowData objectForKey:@"node"] valueForKey:@"title"]]; 95 | 96 | NSString *replies = [NSString stringWithFormat:@"%i",[[rowData valueForKey:@"replies"]intValue]]; 97 | //Using [[rowData valueForKey:@"replies"] directly will lead to an exception. 98 | [cell.replies setText:replies]; 99 | 100 | [cell.username setText:[[rowData objectForKey:@"member"] valueForKey:@"username"]]; 101 | NSString *avatarUrl = [V2EXStringUtil hanldeAvatarURL:[[rowData objectForKey:@"member"] valueForKey:@"avatar_large"]]; 102 | //TODO: Support https 103 | [cell.userAvatar setImageWithURL:[NSURL URLWithString:avatarUrl] placeholderImage:[UIImage imageNamed:@"avatar_large"]]; 104 | 105 | return cell; 106 | } 107 | 108 | #pragma mark - UITableViewDelegate 109 | 110 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 111 | NSUInteger ID = [[[self.data objectAtIndex:indexPath.row] valueForKey:@"id"] intValue]; 112 | _topicIDWillBePushedTo = ID; 113 | [self loadTopicData:ID]; 114 | } 115 | 116 | - (void)requestDataSuccess:(id)dataObject { 117 | if (_loadingStatus == 1) { 118 | self.data = dataObject; 119 | } else if (_loadingStatus == 2) { 120 | // Push to new single topic controller 121 | 122 | [self pushToSingleTopicViewController:dataObject]; 123 | } 124 | 125 | [super requestDataSuccess:dataObject]; 126 | } 127 | 128 | 129 | // 130 | //- (void)requestDataFailure:(NSString *)errorMessage { 131 | // [self showMessage:errorMessage]; 132 | // [self showProgressView]; 133 | //} 134 | 135 | 136 | /* 137 | // Override to support rearranging the table view. 138 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 139 | { 140 | } 141 | */ 142 | 143 | /* 144 | // Override to support conditional rearranging of the table view. 145 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 146 | { 147 | // Return NO if you do not want the item to be re-orderable. 148 | return YES; 149 | } 150 | */ 151 | 152 | /* 153 | #pragma mark - Navigation 154 | 155 | // In a story board-based application, you will often want to do a little preparation before navigation 156 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 157 | { 158 | // Get the new view controller using [segue destinationViewController]. 159 | // Pass the selected object to the new view controller. 160 | } 161 | 162 | */ 163 | 164 | - (IBAction)showMenu:(id)sender { 165 | [self.sideMenuViewController presentMenuViewController]; 166 | } 167 | @end 168 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMenuViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMenuViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RESideMenu.h" 11 | #import "UIViewController+MBProgressHUD.h" 12 | 13 | @interface V2EXMenuViewController : UIViewController 14 | 15 | @property (strong, readwrite, nonatomic) UITableView *tableView; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMenuViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMenuViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXMenuViewController.h" 10 | #import "V2EXAppDelegate.h" 11 | #import "V2EXUserLoginViewController.h" 12 | #import "UIViewController+MBProgressHUD.h" 13 | 14 | #define MENU_BUTTON_COUNT 4 15 | 16 | @interface V2EXMenuViewController () 17 | 18 | @end 19 | 20 | @implementation V2EXMenuViewController 21 | 22 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 23 | { 24 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 25 | if (self) { 26 | // Custom initialization 27 | } 28 | return self; 29 | } 30 | 31 | - (void)viewDidLoad 32 | { 33 | [super viewDidLoad]; 34 | 35 | self.tableView = ({ 36 | UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, (self.view.frame.size.height - 54 * MENU_BUTTON_COUNT) / 2.0f, self.view.frame.size.width, 54 * MENU_BUTTON_COUNT) style:UITableViewStylePlain]; 37 | tableView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; 38 | tableView.delegate = self; 39 | tableView.dataSource = self; 40 | tableView.opaque = NO; 41 | tableView.backgroundColor = [UIColor clearColor]; 42 | 43 | tableView.backgroundView = nil; 44 | tableView.backgroundColor = [UIColor clearColor]; 45 | tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 46 | tableView.bounces = NO; 47 | tableView.scrollsToTop = NO; 48 | tableView; 49 | }); 50 | [self.view addSubview:self.tableView]; 51 | } 52 | 53 | - (void)didReceiveMemoryWarning 54 | { 55 | [super didReceiveMemoryWarning]; 56 | // Dispose of any resources that can be recreated. 57 | } 58 | 59 | #pragma mark - 60 | #pragma mark UITableView Delegate 61 | 62 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 65 | UINavigationController *navigationController = (UINavigationController *)self.sideMenuViewController.contentViewController; 66 | 67 | V2EXAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; 68 | 69 | switch (indexPath.row) { 70 | case 0: 71 | // navigationController.viewControllers = @[[self.storyboard instantiateViewControllerWithIdentifier:@"nodesListController"]]; 72 | navigationController.viewControllers = @[appDelegate.sharedNodesListViewController]; 73 | [self.sideMenuViewController hideMenuViewController]; 74 | break; 75 | case 1: 76 | navigationController.viewControllers = @[appDelegate.sharedLatestTopicsViewController]; 77 | [self.sideMenuViewController hideMenuViewController]; 78 | break; 79 | case 2: 80 | navigationController.viewControllers = @[appDelegate.sharedSelfViewController]; 81 | [self.sideMenuViewController hideMenuViewController]; 82 | break; 83 | case 3: 84 | /* navigationController.viewControllers = @[[self.storyboard instantiateViewControllerWithIdentifier:@"settingsController"]]; 85 | [self.sideMenuViewController hideMenuViewController]; 86 | break; 87 | case 4:*/ 88 | [self doLogout]; 89 | [appDelegate.sharedSelfViewController viewWillAppear:YES]; 90 | break; 91 | default: 92 | break; 93 | } 94 | } 95 | 96 | #pragma mark - 97 | #pragma mark UITableView Datasource 98 | 99 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 100 | { 101 | return 54; 102 | } 103 | 104 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 105 | { 106 | return 1; 107 | } 108 | 109 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex 110 | { 111 | return MENU_BUTTON_COUNT; 112 | } 113 | 114 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 115 | { 116 | static NSString *cellIdentifier = @"Cell"; 117 | 118 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 119 | 120 | if (cell == nil) { 121 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 122 | cell.backgroundColor = [UIColor clearColor]; 123 | cell.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:21]; 124 | cell.textLabel.textColor = [UIColor whiteColor]; 125 | cell.textLabel.highlightedTextColor = [UIColor lightGrayColor]; 126 | cell.selectedBackgroundView = [[UIView alloc] init]; 127 | } 128 | 129 | NSArray *titles = @[@"社区节点", @"最新话题", @"个人中心",/* @"用户设置",*/ @"注销登录"]; 130 | NSArray *images = @[@"IconHome", @"IconRecent", @"IconProfile", /*@"IconSettings",*/ @"IconKey"]; 131 | cell.textLabel.text = titles[indexPath.row]; 132 | cell.imageView.image = [UIImage imageNamed:images[indexPath.row]]; 133 | 134 | return cell; 135 | } 136 | 137 | - (UIStatusBarStyle)preferredStatusBarStyle 138 | { 139 | return UIStatusBarStyleLightContent; 140 | } 141 | 142 | - (void)doLogout { 143 | [V2EXUserLoginViewController logout]; 144 | NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults]; 145 | [userDefaultes setObject:nil forKey:@"saved_username"]; 146 | [userDefaultes setObject:nil forKey:@"saved_password"]; 147 | [userDefaultes synchronize]; 148 | [self showMessage:@"您已登出"]; 149 | } 150 | 151 | 152 | #pragma mark - 153 | #pragma mark RESideMenu Delegate 154 | 155 | - (void)sideMenu:(RESideMenu *)sideMenu willShowMenuViewController:(UIViewController *)menuViewController 156 | { 157 | // NSLog(@"willShowMenuViewController"); 158 | [self hideProgressView]; 159 | } 160 | 161 | - (void)sideMenu:(RESideMenu *)sideMenu didShowMenuViewController:(UIViewController *)menuViewController 162 | { 163 | // NSLog(@"didShowMenuViewController"); 164 | } 165 | 166 | - (void)sideMenu:(RESideMenu *)sideMenu willHideMenuViewController:(UIViewController *)menuViewController 167 | { 168 | // NSLog(@"willHideMenuViewController"); 169 | } 170 | 171 | - (void)sideMenu:(RESideMenu *)sideMenu didHideMenuViewController:(UIViewController *)menuViewController 172 | { 173 | // NSLog(@"didHideMenuViewController"); 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMyRepliesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMyRepliesViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTableViewController.h" 10 | #import "TFHpple+V2EXMethod.h" 11 | 12 | @interface V2EXMyRepliesViewController : V2EXTableViewController 13 | { 14 | NSCache *_cellCache; 15 | NSMutableArray *_tid; 16 | NSString *_cssStyle; 17 | 18 | NSMutableArray *_topicLinkArray; 19 | } 20 | 21 | @property (strong, nonatomic) TFHpple *doc; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMyRepliesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMyRepliesViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXMyRepliesViewController.h" 10 | #import 11 | #import "V2EXStringUtil.h" 12 | #define AttributedTextCellReuseIdentifier @"userRepliesListCell" 13 | 14 | @interface V2EXMyRepliesViewController () 15 | 16 | @end 17 | 18 | @implementation V2EXMyRepliesViewController 19 | 20 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 21 | { 22 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 23 | if (self) { 24 | // Custom initialization 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | 34 | NSBundle *bundle = [NSBundle mainBundle]; 35 | NSString *path = [bundle pathForResource:@"repliesList" ofType:@"css"]; 36 | NSData *cssData = [NSData dataWithContentsOfFile:path]; 37 | _cssStyle = [[@""]; 38 | 39 | _cellCache = [NSCache new]; 40 | [self handleRepliesData]; 41 | 42 | } 43 | 44 | - (void)handleRepliesData { 45 | self.data = [[NSMutableArray alloc] init]; 46 | _topicLinkArray = [[NSMutableArray alloc] init]; 47 | 48 | NSArray *repliesDockArray = [self.doc searchWithXPathQuery:@"//div[@class='dock_area']"]; 49 | NSArray *repliesInnerArray = [self.doc searchWithXPathQuery:@"//div[@class='reply_content']"]; 50 | for (int i = 0; i < [repliesDockArray count]; ++i) { 51 | NSString *row = [[[repliesDockArray objectAtIndex:i] raw] stringByAppendingString:[[repliesInnerArray objectAtIndex:i] raw]]; 52 | [self.data addObject:row]; 53 | 54 | NSString *link = [[[repliesDockArray objectAtIndex:i] searchFirstElementWithXPathQuery:@"//span[@class='gray']/a"] objectForKey:@"href"]; 55 | [_topicLinkArray addObject:link?:@""]; 56 | } 57 | } 58 | 59 | #pragma mark UITableViewDataSource 60 | - (void)configureCell:(DTAttributedTextCell *)cell forIndexPath:(NSIndexPath *)indexPath 61 | { 62 | NSDictionary *rowData = [self.data objectAtIndex:indexPath.row]; 63 | 64 | NSString *formattedContent = [_cssStyle stringByAppendingString:[NSString stringWithFormat:@"
%@
", rowData]]; 65 | [cell setHTMLString:formattedContent]; 66 | 67 | cell.accessoryType = UITableViewCellAccessoryNone; 68 | cell.attributedTextContextView.shouldDrawImages = YES; 69 | } 70 | 71 | - (BOOL)_canReuseCells 72 | { 73 | // reuse does not work for variable height 74 | 75 | if ([self respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) 76 | { 77 | return NO; 78 | } 79 | 80 | // only reuse cells with fixed height 81 | return YES; 82 | } 83 | 84 | - (DTAttributedTextCell *)tableView:(UITableView *)tableView preparedCellForIndexPath:(NSIndexPath *)indexPath 85 | { 86 | // workaround for iOS 5 bug 87 | NSString *key = [NSString stringWithFormat:@"%ld-%ld", (long)indexPath.section, (long)indexPath.row]; 88 | 89 | DTAttributedTextCell *cell = [_cellCache objectForKey:key]; 90 | 91 | if (!cell) 92 | { 93 | if ([self _canReuseCells]) 94 | { 95 | cell = (DTAttributedTextCell *)[tableView dequeueReusableCellWithIdentifier:AttributedTextCellReuseIdentifier]; 96 | } 97 | 98 | if (!cell) 99 | { 100 | cell = [[DTAttributedTextCell alloc] initWithReuseIdentifier:AttributedTextCellReuseIdentifier]; 101 | } 102 | 103 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 104 | cell.hasFixedRowHeight = NO; 105 | 106 | // cache it, if there is a cache 107 | [_cellCache setObject:cell forKey:key]; 108 | } 109 | 110 | [self configureCell:cell forIndexPath:indexPath]; 111 | 112 | return cell; 113 | } 114 | 115 | // disable this method to get static height = better performance 116 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 117 | { 118 | DTAttributedTextCell *cell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath]; 119 | 120 | return [cell requiredRowHeightInTableView:tableView]; 121 | } 122 | 123 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 124 | { 125 | DTAttributedTextCell *cell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath]; 126 | 127 | return cell; 128 | } 129 | 130 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 131 | NSUInteger index = [indexPath row]; 132 | NSUInteger topicID = [V2EXStringUtil link2TopicID:[_topicLinkArray objectAtIndex:index]]; 133 | 134 | if (topicID > 0) { 135 | [self.model getTopicWithID:topicID]; 136 | [self showProgressView]; 137 | } else { 138 | [self showMessage:@"无法加载您的主题"]; 139 | } 140 | } 141 | 142 | - (void)requestDataSuccess:(id)dataObject { 143 | [self pushToSingleTopicViewController:dataObject]; 144 | } 145 | 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMyTopicsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMyTopicsViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTableViewController.h" 10 | #import "TFHpple+V2EXMethod.h" 11 | 12 | @interface V2EXMyTopicsViewController : V2EXTableViewController 13 | 14 | @property (strong, nonatomic) TFHpple *doc; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXMyTopicsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMyTopicsViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXMyTopicsViewController.h" 10 | #import "UIViewController+MBProgressHUD.h" 11 | #import "V2EXUserTopicsListCell.h" 12 | #import "V2EXStringUtil.h" 13 | 14 | @interface V2EXMyTopicsViewController () 15 | 16 | @end 17 | 18 | @implementation V2EXMyTopicsViewController 19 | 20 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 21 | { 22 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 23 | if (self) { 24 | // Custom initialization 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | 33 | if (self.doc == nil) { 34 | [self showMessage:@"未知错误,无法加载您的主题"]; 35 | return; 36 | } 37 | self.tableView.rowHeight = 70; // TODO: Why don't storyboard with identifiertopicListInSingleNodeController support rowHeight? 38 | [self handleUserTopicsListData]; 39 | 40 | } 41 | 42 | - (void)handleUserTopicsListData { 43 | self.data = [[NSMutableArray alloc] init]; 44 | NSArray *topicsArray = [self.doc searchWithXPathQuery:@"//div[@class='content']/div[@class='box']/div[@class='cell item']"]; 45 | 46 | for (TFHppleElement *element in topicsArray) { 47 | NSString *title = [[element searchFirstElementWithXPathQuery:@"//span[@class='item_title']/a"] text]?:@""; 48 | NSString *nodeTitle = [[element searchFirstElementWithXPathQuery:@"//span[@class='small fade']/a[1]"] text]?:@""; 49 | NSString *replies = [[element searchFirstElementWithXPathQuery:@"//a[@class='count_orange']"] text]?:@"0"; 50 | NSString *lastReplyUsername = [[element searchFirstElementWithXPathQuery:@"//span[@class='small fade']/strong[2]/a"] text]?:@"无回复"; 51 | NSString *link = [[element searchFirstElementWithXPathQuery:@"//span[@class='item_title']/a"] objectForKey:@"href"]; 52 | 53 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 54 | title, @"title", 55 | nodeTitle, @"nodeTitle", 56 | replies, @"replies", 57 | lastReplyUsername, @"lastReplyUsername", 58 | link, @"link", 59 | nil]; 60 | [self.data addObject:dict]; 61 | } 62 | } 63 | 64 | - (void)didReceiveMemoryWarning 65 | { 66 | [super didReceiveMemoryWarning]; 67 | // Dispose of any resources that can be recreated. 68 | } 69 | 70 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 71 | { 72 | NSUInteger index = [indexPath row]; 73 | static NSString *CellIdentifier = @"userTopicListCell"; 74 | 75 | UINib *nib = [UINib nibWithNibName:@"V2EXUserTopicsListCell" bundle:nil]; 76 | [tableView registerNib:nib forCellReuseIdentifier:CellIdentifier]; 77 | 78 | V2EXUserTopicsListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 79 | 80 | NSDictionary *rowData = [self.data objectAtIndex:index]; 81 | 82 | cell.title.text = [rowData valueForKey:@"title"]; 83 | cell.nodeTitle.text = [rowData valueForKey:@"nodeTitle"]; 84 | cell.replies.text = [rowData valueForKey:@"replies"]; 85 | cell.lastReplyUsername.text = [rowData valueForKey:@"lastReplyUsername"]; 86 | 87 | return cell; 88 | } 89 | 90 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 91 | NSUInteger index = [indexPath row]; 92 | NSUInteger topicID = [V2EXStringUtil link2TopicID:[[self.data objectAtIndex:index] objectForKey:@"link"]]; 93 | 94 | if (topicID > 0) { 95 | [self.model getTopicWithID:topicID]; 96 | [self showProgressView]; 97 | } else { 98 | [self showMessage:@"无法加载您的主题"]; 99 | } 100 | } 101 | 102 | - (void)requestDataSuccess:(id)dataObject { 103 | [self pushToSingleTopicViewController:dataObject]; 104 | } 105 | 106 | 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXNewTopicViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNewTopicViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/8/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIViewController+MBProgressHUD.h" 11 | #import "V2EXRequestDataDelegate.h" 12 | #import "V2EXNormalModel.h" 13 | #import "V2EXAfterTopicActionDelegate.h" 14 | 15 | @interface V2EXNewTopicViewController : UIViewController 16 | { 17 | CGRect _originalTextViewFrame; 18 | 19 | NSUInteger _once; 20 | NSUInteger _lastRequestTime; 21 | 22 | V2EXNormalModel *_model; 23 | } 24 | 25 | @property (nonatomic, assign) id lastController; 26 | @property (strong, nonatomic) IBOutlet UITextField *topicTitle; 27 | @property (strong, nonatomic) IBOutlet UITextView *topicContent; 28 | @property (strong, nonatomic) IBOutlet UIBarButtonItem *rightButton; 29 | @property (strong, nonatomic) NSString *uri; 30 | 31 | - (IBAction)newTopicButtonClick:(id)sender; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXNewTopicViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNewTopicViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/8/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXNewTopicViewController.h" 10 | #import "TFHpple+V2EXMethod.h" 11 | 12 | @interface V2EXNewTopicViewController () 13 | 14 | @end 15 | 16 | @implementation V2EXNewTopicViewController 17 | 18 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 19 | { 20 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 21 | if (self) { 22 | // Custom initialization 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | [self configureTextViewLayout]; 32 | 33 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 34 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 35 | _originalTextViewFrame = self.topicContent.frame; 36 | 37 | _model = [[V2EXNormalModel alloc] initWithDelegate:self]; 38 | } 39 | 40 | - (void)viewWillAppear:(BOOL)animated { 41 | [self request4OnceCode]; 42 | } 43 | 44 | 45 | - (void)didReceiveMemoryWarning 46 | { 47 | [super didReceiveMemoryWarning]; 48 | // Dispose of any resources that can be recreated. 49 | } 50 | 51 | - (void)configureTextViewLayout { 52 | [self.topicContent.layer setBorderColor:[[[UIColor lightGrayColor] colorWithAlphaComponent:0.3] CGColor]]; 53 | [self.topicContent.layer setBorderWidth:1.0]; 54 | 55 | self.topicContent.layer.cornerRadius = 5; 56 | self.topicContent.clipsToBounds = YES; 57 | } 58 | 59 | - (void)keyboardWillShow:(NSNotification *)notification { 60 | [self moveTextViewForKeyboard:notification up:YES]; 61 | } 62 | 63 | - (void)keyboardWillHide:(NSNotification *)notification { 64 | [self moveTextViewForKeyboard:notification up:NO]; 65 | } 66 | 67 | - (void)moveTextViewForKeyboard:(NSNotification*)notification up:(BOOL)up { 68 | NSDictionary *userInfo = [notification userInfo]; 69 | NSTimeInterval animationDuration; 70 | UIViewAnimationCurve animationCurve; 71 | CGRect keyboardRect; 72 | 73 | [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve]; 74 | animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 75 | keyboardRect = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 76 | keyboardRect = [self.view convertRect:keyboardRect fromView:nil]; 77 | 78 | [UIView beginAnimations:@"ResizeForKeyboard" context:nil]; 79 | [UIView setAnimationDuration:animationDuration]; 80 | [UIView setAnimationCurve:animationCurve]; 81 | 82 | if (up == YES) { 83 | CGFloat keyboardTop = keyboardRect.origin.y; 84 | CGRect newTextViewFrame = self.topicContent.frame; 85 | // originalTextViewFrame = self.replyContent.frame; 86 | newTextViewFrame.size.height = keyboardTop - self.topicContent.frame.origin.y - 5; 87 | 88 | self.topicContent.frame = newTextViewFrame; 89 | } else { 90 | // Keyboard is going away (down) - restore original frame 91 | self.topicContent.frame = _originalTextViewFrame; 92 | } 93 | 94 | [UIView commitAnimations]; 95 | } 96 | 97 | - (IBAction)newTopicButtonClick:(id)sender { 98 | [self showProgressView]; 99 | if ((_lastRequestTime - [[NSDate date] timeIntervalSince1970]) >= 900 100 | || _once <= 0) { 101 | [self request4OnceCode]; 102 | } else { 103 | [self request2PostNewTopic]; 104 | } 105 | } 106 | 107 | - (void)request4OnceCode { 108 | if ([self.uri length] <= 0) { 109 | [self showMessage:@"意外错误,请重试"]; 110 | [self.navigationController popViewControllerAnimated:YES]; 111 | return; 112 | } 113 | [_model getNewTopicPage:self.uri]; 114 | } 115 | 116 | - (void)request2PostNewTopic { 117 | if ([self.topicTitle.text length] <= 0) { 118 | [self showMessage:@"标题不能为空"]; 119 | return; 120 | } 121 | 122 | self.rightButton.enabled = NO; 123 | 124 | NSLog(@"Posting new topic: %@, to node: %@", self.topicTitle.text, self.uri); 125 | [_model newTopic:self.uri andTitle:self.topicTitle.text andContent:self.topicContent.text andOnce:_once]; 126 | } 127 | 128 | - (void)requestDataSuccess:(id)dataObject { 129 | if (_once <= 0) { 130 | [self hanldeOnceCode:dataObject]; 131 | if (self.rightButton.enabled == NO) { 132 | [self request2PostNewTopic]; 133 | } 134 | } else { 135 | [self handlePostRespondData:dataObject]; 136 | } 137 | } 138 | 139 | - (void)hanldeOnceCode:(NSData *)data { 140 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data]; 141 | NSString *onceCode = [[doc searchFirstElementWithXPathQuery:@"//input[@name='once']"] objectForKey:@"value"]; 142 | _once = (NSUInteger)[onceCode integerValue]; 143 | } 144 | 145 | - (void)handlePostRespondData:(NSData *)data { 146 | self.rightButton.enabled = YES; 147 | [self hideProgressView]; 148 | [self.navigationController popViewControllerAnimated:NO]; 149 | 150 | 151 | // TODO: Validation for new topic 152 | // TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data]; 153 | // NSString *newTopicTitle = [[doc searchFirstElementWithXPathQuery:@"//div[@class='header']/h1"] text]; 154 | // if (newTopicTitle == self.topicTitle.text) { 155 | 156 | // BOOL isSuccess = [[@" " stringByAppendingString:newTopicTitle] stringByAppendingString:@" "] == self.topicTitle.text; 157 | [self.lastController afterCreateTopic:data]; 158 | NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 159 | // } 160 | } 161 | 162 | - (void)requestDataFailure:(NSString *)errorMessage { 163 | _once = 0; 164 | self.rightButton.enabled = YES; 165 | } 166 | 167 | - (void)setOnceValue:(NSUInteger)once { 168 | _once = once; 169 | _lastRequestTime = [[NSDate date] timeIntervalSince1970]; 170 | } 171 | 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXNodesListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNodesListViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "DRPaginatedScrollView.h" 13 | #import "V2EXNodesListModel.h" 14 | #import "V2EXRequestDataDelegate.h" 15 | #import "UIViewController+MBProgressHUD.h" 16 | #import "V2EXNormalModel.h" 17 | #import "V2EXTopicsListInSingleNodeViewController.h" 18 | 19 | 20 | @interface V2EXNodesListViewController : UIViewController 21 | { 22 | CGFloat _segmentedControlX; 23 | V2EXNormalModel *_normalModel; 24 | 25 | NSMutableString *_uriClicked; 26 | 27 | NSMutableString *_loadingNodeTitle; 28 | } 29 | 30 | @property (strong, nonatomic) DRPaginatedScrollView * paginatedScrollView; 31 | @property (strong, nonatomic) HMSegmentedControl *segmentedControl; 32 | 33 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel0; 34 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel1; 35 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel2; 36 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel3; 37 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel4; 38 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel5; 39 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel6; 40 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel7; 41 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel8; 42 | @property (nonatomic, retain) V2EXNodesListModel *nodesListModel9; 43 | 44 | + (V2EXNodesListViewController *)sharedController; 45 | - (IBAction)showMenu:(id)sender; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXNotificationCenterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNotificationCenterViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTableViewController.h" 10 | 11 | @interface V2EXNotificationCenterViewController : V2EXTableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXNotificationCenterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNotificationCenterViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/20/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXNotificationCenterViewController.h" 10 | 11 | @interface V2EXNotificationCenterViewController () 12 | 13 | @end 14 | 15 | @implementation V2EXNotificationCenterViewController 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Custom initialization 22 | } 23 | return self; 24 | } 25 | 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | // Do any additional setup after loading the view. 30 | } 31 | 32 | - (void)didReceiveMemoryWarning 33 | { 34 | [super didReceiveMemoryWarning]; 35 | // Dispose of any resources that can be recreated. 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXReplyTopicViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXReplyTopicViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/5/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "V2EXNormalModel.h" 11 | #import "V2EXAfterTopicActionDelegate.h" 12 | 13 | @interface V2EXReplyTopicViewController : UIViewController 14 | { 15 | V2EXNormalModel *_model; 16 | 17 | CGRect _originalTextViewFrame; 18 | } 19 | 20 | @property (nonatomic, assign) id lastController; 21 | @property (strong, nonatomic) IBOutlet UITextView *replyContent; 22 | @property (nonatomic) NSUInteger topicID; 23 | @property (nonatomic) NSUInteger onceCode; 24 | 25 | - (IBAction)doReply:(id)sender; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXReplyTopicViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXReplyTopicViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/5/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXReplyTopicViewController.h" 10 | #import "UIViewController+MBProgressHUD.h" 11 | #import "TFHpple+V2EXMethod.h" 12 | 13 | @interface V2EXReplyTopicViewController () 14 | 15 | @end 16 | 17 | @implementation V2EXReplyTopicViewController 18 | 19 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 20 | { 21 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 22 | if (self) { 23 | // Custom initialization 24 | } 25 | return self; 26 | } 27 | 28 | - (void)viewDidLoad 29 | { 30 | [super viewDidLoad]; 31 | 32 | if (self.topicID <= 0 || self.onceCode <= 0) { 33 | [self showMessage:@"您尚未登录,不能回复"]; 34 | [self.navigationController popViewControllerAnimated:YES]; 35 | return; 36 | } 37 | 38 | [self configureTextViewLayout]; 39 | 40 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 41 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 42 | _originalTextViewFrame = self.replyContent.frame; 43 | 44 | _model = [[V2EXNormalModel alloc] initWithDelegate:self]; 45 | } 46 | 47 | - (void)viewDidAppear:(BOOL)animated { 48 | // [self.replyContent becomeFirstResponder]; 49 | } 50 | 51 | - (void)keyboardWillShow:(NSNotification *)notification { 52 | [self moveTextViewForKeyboard:notification up:YES]; 53 | } 54 | 55 | - (void)keyboardWillHide:(NSNotification *)notification { 56 | [self moveTextViewForKeyboard:notification up:NO]; 57 | } 58 | 59 | - (void)moveTextViewForKeyboard:(NSNotification*)notification up:(BOOL)up { 60 | NSDictionary *userInfo = [notification userInfo]; 61 | NSTimeInterval animationDuration; 62 | UIViewAnimationCurve animationCurve; 63 | CGRect keyboardRect; 64 | 65 | [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve]; 66 | animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 67 | keyboardRect = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 68 | keyboardRect = [self.view convertRect:keyboardRect fromView:nil]; 69 | 70 | [UIView beginAnimations:@"ResizeForKeyboard" context:nil]; 71 | [UIView setAnimationDuration:animationDuration]; 72 | [UIView setAnimationCurve:animationCurve]; 73 | 74 | if (up == YES) { 75 | CGFloat keyboardTop = keyboardRect.origin.y; 76 | CGRect newTextViewFrame = self.replyContent.frame; 77 | // originalTextViewFrame = self.replyContent.frame; 78 | newTextViewFrame.size.height = keyboardTop - self.replyContent.frame.origin.y - 5; 79 | 80 | self.replyContent.frame = newTextViewFrame; 81 | } else { 82 | // Keyboard is going away (down) - restore original frame 83 | self.replyContent.frame = _originalTextViewFrame; 84 | } 85 | 86 | [UIView commitAnimations]; 87 | } 88 | 89 | - (void)requestDataSuccess:(id)dataObject { 90 | [self hideProgressView]; 91 | 92 | TFHppleElement *replyFormElement = [[[TFHpple alloc] initWithHTMLData:dataObject] searchFirstElementWithXPathQuery:@"//div[@class='box']//div[@class='cell']//form"]; 93 | 94 | if (replyFormElement) { 95 | NSUInteger responseTopicID = (NSUInteger)[[[replyFormElement objectForKey:@"action"] stringByReplacingOccurrencesOfString:@"/t/" withString:@""] integerValue]; 96 | if (responseTopicID == self.topicID && responseTopicID > 0) { 97 | [self.lastController afterReplyTopic:dataObject]; 98 | [self showMessage:@"回复成功"]; 99 | [self.navigationController popViewControllerAnimated:YES]; 100 | return; 101 | } 102 | } 103 | 104 | [self showMessage:@"回复失败,请重试"]; 105 | return; 106 | } 107 | 108 | - (IBAction)doReply:(id)sender { 109 | if ([self.replyContent.text length] > 0) { 110 | [self showProgressView]; 111 | [_model replyTopic:self.topicID andOnce:self.onceCode andContent:self.replyContent.text]; 112 | } else { 113 | [self showMessage:@"回复内容不能为空"]; 114 | } 115 | } 116 | 117 | - (void)configureTextViewLayout { 118 | [self.replyContent.layer setBorderColor:[[[UIColor lightGrayColor] colorWithAlphaComponent:0.3] CGColor]]; 119 | [self.replyContent.layer setBorderWidth:1.0]; 120 | 121 | self.replyContent.layer.cornerRadius = 5; 122 | self.replyContent.clipsToBounds = YES; 123 | } 124 | 125 | 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXRootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RESideMenu.h" 11 | 12 | @interface V2EXRootViewController : RESideMenu 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXRootViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXRootViewController.h" 10 | #import "V2EXMenuViewController.h" 11 | 12 | @interface V2EXRootViewController () 13 | 14 | @end 15 | 16 | @implementation V2EXRootViewController 17 | 18 | - (void)awakeFromNib 19 | { 20 | self.contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"contentController"]; 21 | // The first sub view controller is according to contentController 22 | 23 | self.menuViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"menuController"]; 24 | self.backgroundImage = [UIImage imageNamed:@"Stars"]; 25 | self.delegate = (V2EXMenuViewController *)self.menuViewController; 26 | } 27 | 28 | - (void)viewDidLoad 29 | { 30 | [super viewDidLoad]; 31 | // Do any additional setup after loading the view, typically from a nib. 32 | } 33 | 34 | - (void)didReceiveMemoryWarning 35 | { 36 | [super didReceiveMemoryWarning]; 37 | // Dispose of any resources that can be recreated. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXSelfViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXSelfViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/1/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RESideMenu.h" 11 | #import "V2EXNormalModel.h" 12 | #import 13 | 14 | @interface V2EXSelfViewController : UIViewController 15 | { 16 | NSUInteger _loadingStatus; // 0 - No loading ; 1 - Loading my topics ; 2 - Loading my replies ; 3 - Loading notification ; 4 - Self infomation 17 | NSTimeInterval _lastLoadUserInformationTime; 18 | V2EXNormalModel *_model; 19 | 20 | NSMutableString *_currentUsername; 21 | } 22 | @property (strong, nonatomic) IBOutlet UILabel *usernameLabel; 23 | @property (strong, nonatomic) IBOutlet UIImageView *avatar; 24 | @property (strong, nonatomic) IBOutlet UILabel *userMetaLabel; 25 | //@property (strong, nonatomic) IBOutlet DTAttributedLabel *userSocialInformationLabel; 26 | @property (strong, nonatomic) IBOutlet UIButton *myTopicsButton; 27 | @property (strong, nonatomic) IBOutlet UIButton *myRepliesButton; 28 | @property (strong, nonatomic) IBOutlet UIButton *notificationCenterButton; 29 | 30 | + (V2EXSelfViewController *)sharedController; 31 | - (IBAction)showMenu:(id)sender; 32 | - (IBAction)myTopicsButtonClick:(id)sender; 33 | - (IBAction)myRepliesButtonClick:(id)sender; 34 | - (IBAction)notificationCenterButtonClick:(id)sender; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXSelfViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXSelfViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/1/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXSelfViewController.h" 10 | #import "V2EXUserLoginViewController.h" 11 | #import "UIViewController+MBProgressHUD.h" 12 | #import "TFHpple+V2EXMethod.h" 13 | #import 14 | #import 15 | 16 | #import "V2EXMyTopicsViewController.h" 17 | #import "V2EXMyRepliesViewController.h" 18 | #import "V2EXNotificationCenterViewController.h" 19 | 20 | @interface V2EXSelfViewController () 21 | 22 | @end 23 | 24 | @implementation V2EXSelfViewController 25 | 26 | + (V2EXSelfViewController *)sharedController 27 | { 28 | static V2EXSelfViewController *_sharedLatestTopicsViewControllerInstance = nil; 29 | static dispatch_once_t predicate; 30 | dispatch_once(&predicate, ^{ 31 | _sharedLatestTopicsViewControllerInstance = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"selfController"]; 32 | }); 33 | 34 | return _sharedLatestTopicsViewControllerInstance; 35 | } 36 | 37 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 38 | { 39 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 40 | if (self) { 41 | // Custom initialization 42 | } 43 | return self; 44 | } 45 | 46 | - (void)viewDidLoad 47 | { 48 | [super viewDidLoad]; 49 | _model = [[V2EXNormalModel alloc] initWithDelegate:self]; 50 | _lastLoadUserInformationTime = 0; 51 | } 52 | 53 | - (void)viewWillAppear:(BOOL)animated { 54 | [self prepareForUserInfo]; 55 | } 56 | 57 | - (void)prepareForUserInfo { 58 | NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults]; 59 | if (![[[userDefaultes stringForKey:@"saved_username"] uppercaseString] isEqualToString:[_currentUsername uppercaseString]]) { 60 | _lastLoadUserInformationTime = 0; 61 | NSLog(@"'%@' set '%@'", [[userDefaultes stringForKey:@"saved_username"] uppercaseString], [_currentUsername uppercaseString]); 62 | } 63 | _currentUsername = [[userDefaultes stringForKey:@"saved_username"] mutableCopy]; 64 | 65 | if (([[NSDate date] timeIntervalSince1970] - _lastLoadUserInformationTime) > 86400) { 66 | // TODO: Produce more viable way to decide if reload data 67 | _loadingStatus = 4; 68 | 69 | if (_currentUsername) { 70 | [_model getUserInfo:_currentUsername]; 71 | [self showProgressView]; 72 | } else { 73 | [self pushToUserLoginController]; 74 | } 75 | } 76 | } 77 | 78 | - (void)pushToUserLoginController { 79 | [self showMessage:@"您尚未登录,请登录"]; 80 | V2EXUserLoginViewController *userLoginController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"userLoginController"]; 81 | userLoginController.isLoadFromSeflController = YES; 82 | [self.navigationController pushViewController:userLoginController animated:YES]; 83 | } 84 | 85 | 86 | - (void)requestDataSuccess:(id)dataObject { 87 | switch (_loadingStatus) { 88 | case 1: 89 | { 90 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:dataObject]; 91 | V2EXMyTopicsViewController *myTopicsController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"myTopicsController"]; 92 | [myTopicsController setDoc:doc]; 93 | [self.navigationController pushViewController:myTopicsController animated:YES]; 94 | } 95 | break; 96 | case 2: 97 | { 98 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:dataObject]; 99 | V2EXMyRepliesViewController *myTopicsController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"myRepliesController"]; 100 | [myTopicsController setDoc:doc]; 101 | [self.navigationController pushViewController:myTopicsController animated:YES]; 102 | } 103 | break; 104 | case 4: 105 | { 106 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:dataObject]; 107 | if ([doc checkLogin]) { 108 | [self handleWithUserPage:doc]; 109 | } else { 110 | [self pushToUserLoginController]; 111 | } 112 | } 113 | break; 114 | default: 115 | break; 116 | } 117 | 118 | [self enableAllButtons]; 119 | } 120 | 121 | 122 | 123 | - (void)requestDataFailure:(NSString *)errorMessage { 124 | [self enableAllButtons]; 125 | } 126 | 127 | - (void)handleWithUserPage:(TFHpple *)doc { 128 | _lastLoadUserInformationTime = [[NSDate date] timeIntervalSince1970]; 129 | 130 | self.usernameLabel.text = [self getValueForDoc:doc withXpath:@"//div[@class='inner']/table//tr/td[3]/h1"]; 131 | 132 | NSURL *avatarURL = [NSURL URLWithString:[@"https:" stringByAppendingString:[[doc searchFirstElementWithXPathQuery:@"//img[@class='avatar']"] objectForKey:@"src"]]]; 133 | // TODO: https 134 | [self.avatar setImageWithURL:avatarURL placeholderImage:[UIImage imageNamed:@"avatar_large"]]; 135 | 136 | self.userMetaLabel.text = [[self getValueForDoc:doc withXpath:@"//table//td[@width='auto']//span[@class='gray']"] stringByReplacingOccurrencesOfString:@"V2EX " withString:@""]; 137 | 138 | // NSString *socialInformationHTML = [[[doc searchFirstElementWithXPathQuery:@"//div[@class='inner']"] raw] stringByReplacingOccurrencesOfString:@" src=\"" withString:@"style=\"color:black;\" src=\"http://v2ex.com"]; 139 | // self.userSocialInformationLabel.attributedString = [[NSAttributedString alloc] initWithHTMLData:[socialInformationHTML dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:NULL]; 140 | 141 | } 142 | 143 | - (NSString *)getValueForDoc:(TFHpple *)doc withXpath:(NSString *)xpath { 144 | return [[doc searchFirstElementWithXPathQuery:xpath] text]; 145 | } 146 | 147 | // Next controllers: 148 | 149 | - (IBAction)showMenu:(id)sender { 150 | [self.sideMenuViewController presentMenuViewController]; 151 | } 152 | 153 | - (IBAction)myTopicsButtonClick:(id)sender { 154 | [self disableAllButtons]; 155 | _loadingStatus = 1; 156 | [_model getUserTopics:_currentUsername]; 157 | } 158 | 159 | - (IBAction)myRepliesButtonClick:(id)sender { 160 | [self disableAllButtons]; 161 | _loadingStatus = 2; 162 | [_model getUserReplies:_currentUsername]; 163 | } 164 | 165 | - (IBAction)notificationCenterButtonClick:(id)sender { 166 | [self disableAllButtons]; 167 | _loadingStatus = 3; 168 | NSLog(@"Nothing 2 do"); 169 | } 170 | 171 | - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { 172 | return NO; 173 | } 174 | 175 | - (void)disableAllButtons { 176 | self.myTopicsButton.enabled = self.myRepliesButton.enabled = self.notificationCenterButton.enabled = NO; 177 | [self showProgressView]; 178 | } 179 | 180 | - (void)enableAllButtons { 181 | _loadingStatus = 0; 182 | self.myTopicsButton.enabled = self.myRepliesButton.enabled = self.notificationCenterButton.enabled = YES; 183 | // [self hideProgressView]; 184 | } 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXSettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXSettingsViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RESideMenu.h" 11 | 12 | @interface V2EXSettingsViewController : UIViewController 13 | 14 | + (V2EXSettingsViewController *)sharedController; 15 | - (IBAction)showMenu:(id)sender; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXSettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXSettingsViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXSettingsViewController.h" 10 | #import 11 | 12 | @interface V2EXSettingsViewController () 13 | 14 | @end 15 | 16 | @implementation V2EXSettingsViewController 17 | 18 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 19 | { 20 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 21 | if (self) { 22 | // Custom initialization 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | // Do any additional setup after loading the view. 31 | 32 | V2EXNormalModel *model = [[V2EXNormalModel alloc] initWithDelegate:self]; 33 | [model getIndex]; 34 | } 35 | 36 | - (void)requestDataSuccess:(id)dataObject { 37 | NSLog(@"%@", [[NSString alloc] initWithData:dataObject encoding:NSUTF8StringEncoding]); 38 | } 39 | 40 | - (void)didReceiveMemoryWarning 41 | { 42 | [super didReceiveMemoryWarning]; 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | + (V2EXSettingsViewController *)sharedController 47 | { 48 | static V2EXSettingsViewController *_sharedSettingsViewControllerInstance = nil; 49 | static dispatch_once_t predicate; 50 | dispatch_once(&predicate, ^{ 51 | _sharedSettingsViewControllerInstance = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"settingsController"]; 52 | }); 53 | 54 | return _sharedSettingsViewControllerInstance; 55 | } 56 | 57 | - (IBAction)showMenu:(id)sender { 58 | [self.sideMenuViewController presentMenuViewController]; 59 | } 60 | @end 61 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXSingleTopicViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXSingleTopicViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/19/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTableViewController.h" 10 | #import "V2EXAfterTopicActionDelegate.h" 11 | 12 | @interface V2EXSingleTopicViewController : V2EXTableViewController 13 | { 14 | // NSMutableArray *_cellHeightArray; 15 | NSUInteger _topicID; 16 | NSUInteger _onceCode; 17 | NSCache *_cellCache; 18 | 19 | NSUInteger _modelStatus; // 1 - topic; 2 - report 20 | } 21 | 22 | + (V2EXSingleTopicViewController *)sharedController; 23 | 24 | - (void)loadNewTopicWithID:(NSUInteger)ID; 25 | - (void)loadNewTopicWithData:(NSData *)data; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTableViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/7/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "V2EXRequestDataDelegate.h" 12 | #import "UIViewController+MBProgressHUD.h" 13 | #import "V2EXNormalModel.h" 14 | 15 | @interface V2EXTableViewController : UITableViewController 16 | { 17 | NSUInteger _loadingStatus; // 0 - No loading ; 1 - Loading topic list ; 2 - Loading single topic 18 | NSUInteger _topicIDWillBePushedTo; 19 | } 20 | 21 | @property (nonatomic, strong) NSMutableArray* data; 22 | @property (nonatomic, strong) V2EXNormalModel *model; 23 | 24 | - (BOOL)canStartNewLoading; 25 | - (void)pushToSingleTopicViewController:(NSData *)dataObject; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTableViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/7/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTableViewController.h" 10 | #import "V2EXSingleTopicViewController.h" 11 | #import "TFHpple+V2EXMethod.h" 12 | #import "V2EXUserLoginViewController.h" 13 | #import "UIViewController+V2EXJump.h" 14 | 15 | @interface V2EXTableViewController () 16 | 17 | @end 18 | 19 | @implementation V2EXTableViewController 20 | 21 | - (id)initWithStyle:(UITableViewStyle)style 22 | { 23 | self = [super initWithStyle:style]; 24 | if (self) { 25 | // Custom initialization 26 | } 27 | return self; 28 | } 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | 34 | // Uncomment the following line to preserve selection between presentations. 35 | // self.clearsSelectionOnViewWillAppear = NO; 36 | 37 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller. 38 | // self.navigationItem.rightBarButtonItem = self.editButtonItem; 39 | 40 | // Refresh control 41 | UIRefreshControl *rc = [[UIRefreshControl alloc] init]; 42 | rc.attributedTitle = [[NSAttributedString alloc] initWithString:@"下拉刷新"]; 43 | [rc addTarget:self action:@selector(refreshTableView) forControlEvents:UIControlEventValueChanged]; 44 | self.refreshControl = rc; 45 | self.tableView.separatorInset = UIEdgeInsetsZero; 46 | 47 | // Data model 48 | if(!_model){ 49 | _model = [[V2EXNormalModel alloc]initWithDelegate:self]; 50 | } 51 | } 52 | 53 | 54 | - (void)refreshTableView { 55 | if(self.refreshControl.refreshing){ 56 | self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"加载中"]; 57 | [self loadData]; 58 | } 59 | } 60 | 61 | - (void)loadData { 62 | // Must implement this method in subclass. 63 | } 64 | 65 | - (void)didReceiveMemoryWarning 66 | { 67 | [super didReceiveMemoryWarning]; 68 | // Dispose of any resources that can be recreated. 69 | } 70 | 71 | #pragma mark - LoadData 72 | 73 | - (void)requestDataSuccess:(NSDictionary *)dataObject { 74 | [self.tableView reloadData]; 75 | [self hideProgressView]; 76 | 77 | [self finishRefresh]; 78 | _loadingStatus = 0; 79 | } 80 | 81 | - (void)requestDataFailure:(NSString *)errorMessage { 82 | [self finishRefresh]; 83 | _loadingStatus = 0; 84 | } 85 | 86 | - (BOOL)canStartNewLoading { 87 | return _loadingStatus == 0; 88 | } 89 | 90 | - (void)finishRefresh { 91 | // Refresh Control 92 | [self.refreshControl endRefreshing]; 93 | self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"加载完成"]; 94 | [self performSelector:@selector(afterFinishRefresh) withObject:nil afterDelay:0.5f]; 95 | } 96 | 97 | - (void)afterFinishRefresh { 98 | self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"下拉刷新"]; 99 | } 100 | 101 | #pragma mark - Table view data source 102 | 103 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 104 | { 105 | return 1; 106 | } 107 | 108 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 109 | { 110 | // Return the number of rows in the section. 111 | return [self.data count]; 112 | } 113 | 114 | #pragma mark - Push to new controller method 115 | - (void)pushToSingleTopicViewController:(NSData *)dataObject { 116 | // Check if need to log in 117 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:dataObject]; 118 | NSArray *messageArray = [doc searchWithXPathQuery:@"//div[@id='Wrapper']/div[@class='content']/div[@class='box']/div[@class='message']"]; 119 | if ([messageArray count] > 0) { 120 | if ([[[messageArray objectAtIndex:0] text] isEqualToString:@"查看本主题需要登录"]) { 121 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"查看本主题需要登录" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; 122 | [alert show]; 123 | // If not login, show alert 124 | // TODO: still request to login if has logged in 125 | return; 126 | } 127 | } 128 | 129 | V2EXSingleTopicViewController *singleTopicController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"singleTopicController"]; 130 | [singleTopicController loadNewTopicWithData:dataObject]; 131 | [self.navigationController pushViewController:singleTopicController animated:YES]; 132 | } 133 | 134 | #pragma marks -- UIAlertViewDelegate -- 135 | -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 136 | { 137 | if (buttonIndex == 1) { 138 | [self pushToUserLoginController]; 139 | } 140 | } 141 | 142 | //- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 143 | //{ 144 | // static NSString *CellIdentifier = @"Cell"; 145 | // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 146 | // 147 | // // Configure the cell... 148 | // 149 | // return cell; 150 | //} 151 | 152 | /* 153 | // Override to support conditional editing of the table view. 154 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 155 | { 156 | // Return NO if you do not want the specified item to be editable. 157 | return YES; 158 | } 159 | */ 160 | 161 | /* 162 | // Override to support editing the table view. 163 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 164 | { 165 | if (editingStyle == UITableViewCellEditingStyleDelete) { 166 | // Delete the row from the data source 167 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 168 | } 169 | else if (editingStyle == UITableViewCellEditingStyleInsert) { 170 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 171 | } 172 | } 173 | */ 174 | 175 | /* 176 | // Override to support rearranging the table view. 177 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 178 | { 179 | } 180 | */ 181 | 182 | /* 183 | // Override to support conditional rearranging of the table view. 184 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 185 | { 186 | // Return NO if you do not want the item to be re-orderable. 187 | return YES; 188 | } 189 | */ 190 | 191 | /* 192 | #pragma mark - Navigation 193 | 194 | // In a story board-based application, you will often want to do a little preparation before navigation 195 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 196 | { 197 | // Get the new view controller using [segue destinationViewController]. 198 | // Pass the selected object to the new view controller. 199 | } 200 | 201 | */ 202 | 203 | @end 204 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXTopicsListInSingleNodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTopicsListInSingleNodeViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/16/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "V2EXTableViewController.h" 12 | #import "V2EXSingleTopicViewController.h" 13 | #import "V2EXAfterTopicActionDelegate.h" 14 | 15 | @interface V2EXTopicsListInSingleNodeViewController : V2EXTableViewController 16 | { 17 | BOOL _isLogin; 18 | } 19 | 20 | @property (weak, nonatomic) NSMutableString *uri; 21 | 22 | //+ (V2EXTopicsListInSingleNodeViewController *)sharedController; 23 | - (void)loadNewNodeWithData:(NSData *)data; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXTopicsListInSingleNodeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTopicsListInSingleNodeViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/16/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "TFHpple+V2EXMethod.h" 10 | #import "V2EXTopicsListInSingleNodeViewController.h" 11 | #import "V2EXTopicsListCell.h" 12 | #import "V2EXNormalModel.h" 13 | #import "V2EXMBProgressHUDUtil.h" 14 | #import "V2EXStringUtil.h" 15 | #import "V2EXNewTopicViewController.h" 16 | 17 | @interface V2EXTopicsListInSingleNodeViewController () 18 | 19 | @end 20 | 21 | @implementation V2EXTopicsListInSingleNodeViewController 22 | 23 | //+ (V2EXTopicsListInSingleNodeViewController *)sharedController 24 | //{ 25 | // static V2EXTopicsListInSingleNodeViewController *_sharedTopicsListInSingleNodeViewControllerInstance = nil; 26 | // static dispatch_once_t predicate; 27 | // dispatch_once(&predicate, ^{ 28 | // _sharedTopicsListInSingleNodeViewControllerInstance = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"topicListInSingleNodeController"]; 29 | // }); 30 | // 31 | // return _sharedTopicsListInSingleNodeViewControllerInstance; 32 | //} 33 | 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | self.tableView.rowHeight = 70; // TODO: Why don't storyboard with identifiertopicListInSingleNodeController support rowHeight? 39 | _loadingStatus = 1; 40 | 41 | // self.singleTopicViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"singleTopicController"]; 42 | } 43 | 44 | 45 | - (void)didReceiveMemoryWarning 46 | { 47 | [super didReceiveMemoryWarning]; 48 | // Dispose of any resources that can be recreated. 49 | } 50 | 51 | //- (void)viewWillAppear:(BOOL)animated { 52 | //} 53 | 54 | 55 | - (void)loadNewNodeWithData:(NSData *)data { 56 | // Reload data 57 | _loadingStatus = 1; 58 | [self requestDataSuccess:data]; 59 | 60 | // Scroll to the top 61 | // [self.tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO]; 62 | } 63 | 64 | - (void)loadData { 65 | if ([self canStartNewLoading]) { 66 | _loadingStatus = 1; 67 | [self.model getTopicsList:self.uri]; 68 | } 69 | } 70 | 71 | - (void)loadTopic:(NSUInteger)ID { 72 | if ([self canStartNewLoading]) { 73 | _loadingStatus = 2; 74 | [self showProgressView]; 75 | 76 | [self.model getTopicWithID:ID]; 77 | } 78 | } 79 | 80 | - (void)requestDataSuccess:(id)dataObject { 81 | if (_loadingStatus == 1) { 82 | [self handleListData:dataObject]; 83 | } else { 84 | [self pushToSingleTopicViewController:dataObject]; 85 | } 86 | [super requestDataSuccess:dataObject]; 87 | 88 | } 89 | 90 | 91 | - (void)requestDataFailure:(NSString *)errorMessage { 92 | [super requestDataFailure:errorMessage]; 93 | } 94 | 95 | - (void) handleListData:(id)dataObject { 96 | self.data = [[NSMutableArray alloc] init]; 97 | 98 | TFHpple *doc = [[TFHpple alloc]initWithHTMLData:dataObject]; 99 | 100 | //Check login 101 | _isLogin = [doc checkLogin]; 102 | 103 | // Data Rows 104 | NSArray *elements = [doc searchWithXPathQuery:@"//body/div[2]/div/div/div[@class='cell']/table[1]"]; 105 | 106 | for (TFHppleElement *element in elements) { 107 | TFHppleElement *avatarElement = [element searchFirstElementWithXPathQuery:@"//td[1]/a/img"]; 108 | TFHppleElement *titleElement = [element searchFirstElementWithXPathQuery:@"//td[3]/span[@class='item_title']/a"]; 109 | TFHppleElement *userNameElement = [element searchFirstElementWithXPathQuery:@"//td[3]/span[@class='small fade']/strong"]; 110 | NSArray *replyElements = [element searchWithXPathQuery:@"//td[4]/a"]; 111 | 112 | // Handle reply count 113 | NSString *replyCount; 114 | if ([replyElements count] > 0) 115 | { 116 | TFHppleElement *replyElement = [replyElements objectAtIndex:0]; 117 | replyCount = [replyElement text]; 118 | } else { 119 | replyCount = @"0"; 120 | } 121 | 122 | NSString *link = [[element searchFirstElementWithXPathQuery:@"//td[3]/span[@class='item_title']/a"] objectForKey:@"href"]; 123 | 124 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 125 | [V2EXStringUtil hanldeAvatarURL:[avatarElement objectForKey:@"src"]], @"avatar", 126 | [titleElement text], @"title", 127 | [userNameElement text], @"username", 128 | replyCount, @"replies", 129 | link, @"link", nil 130 | ]; 131 | [self.data addObject:dict]; 132 | } 133 | } 134 | 135 | #pragma mark - Table view data source 136 | 137 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 138 | { 139 | NSUInteger index = [indexPath row]; 140 | static NSString *CellIdentifier = @"topicsListCell"; 141 | 142 | UINib *nib = [UINib nibWithNibName:@"V2EXTopicsListCell" bundle:nil]; 143 | [tableView registerNib:nib forCellReuseIdentifier:CellIdentifier]; 144 | 145 | V2EXTopicsListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 146 | 147 | id rowData = [self.data objectAtIndex:index]; 148 | 149 | cell.title.text = [rowData valueForKey:@"title"]; 150 | cell.nodeTitle.text = @""; 151 | cell.replies.text = [rowData valueForKey:@"replies"]; 152 | cell.username.text = [rowData valueForKey:@"username"]; 153 | [cell.userAvatar setImageWithURL:[NSURL URLWithString:[rowData valueForKey:@"avatar"]] placeholderImage:[UIImage imageNamed:@"avatar_large"]]; 154 | 155 | return cell; 156 | } 157 | 158 | - (NSUInteger)link2TopicID:(NSString *) urlString{ 159 | NSError *error; 160 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"/t/[0-9]+#reply" 161 | options:0 162 | error:&error]; 163 | if (regex != nil) { 164 | NSArray *array = [regex matchesInString: urlString 165 | options: 0 166 | range: NSMakeRange( 0, [urlString length])]; 167 | if ([array count] > 0) { 168 | NSTextCheckingResult *match = [array objectAtIndex:0]; 169 | NSRange firstHalfRange = [match rangeAtIndex:0]; 170 | NSString *result = [[[urlString substringWithRange:firstHalfRange] stringByReplacingOccurrencesOfString:@"/t/" withString:@""] stringByReplacingOccurrencesOfString:@"#reply" withString:@""]; 171 | return (NSUInteger)[result integerValue]; 172 | } else { 173 | return 0; 174 | } 175 | } else { 176 | return 0; 177 | } 178 | } 179 | 180 | #pragma mark - Table view delegate 181 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 182 | NSUInteger topicID = [self link2TopicID:[[self.data objectAtIndex:[indexPath row]] objectForKey:@"link"]]; 183 | _topicIDWillBePushedTo = topicID; 184 | [self loadTopic:topicID]; 185 | } 186 | 187 | #pragma mark - Segue 188 | - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { 189 | if ([identifier isEqualToString:@"toNewTopicController"]) { 190 | if (_isLogin) { 191 | return YES; 192 | } 193 | } 194 | [self showMessage:@"无法回复,可能因为您尚未登录"]; 195 | return NO; 196 | } 197 | 198 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 199 | if ([[segue identifier] isEqualToString:@"toNewTopicController"]) { 200 | V2EXNewTopicViewController *newTopicController = [segue destinationViewController]; 201 | newTopicController.uri = self.uri; 202 | newTopicController.lastController = self; 203 | } 204 | } 205 | 206 | #pragma mark - After Post New Topic 207 | - (void)afterCreateTopic:(NSData *)data { 208 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data]; 209 | NSUInteger topicID = (NSUInteger)[[[[doc searchFirstElementWithXPathQuery:@"//form"] objectForKey:@"action"] stringByReplacingOccurrencesOfString:@"/t/" withString:@""] integerValue]; 210 | if (topicID > 0) { 211 | [self showMessage:@"新主题已创建"]; 212 | [self pushToSingleTopicViewController:data]; 213 | } else { 214 | [self showMessage:@"主题发布失败"]; 215 | } 216 | } 217 | 218 | @end 219 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXUserLoginViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXUserLoginViewController.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/1/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "V2EXApiClient.h" 11 | 12 | @interface V2EXUserLoginViewController : UIViewController 13 | { 14 | //AFNetWorking 15 | AFHTTPSessionManager *_manager; 16 | AFHTTPRequestSerializer *_requestSerializer; 17 | AFHTTPResponseSerializer *_responseSerializer; 18 | } 19 | 20 | @property BOOL isLoadFromSeflController; 21 | @property (strong, nonatomic) IBOutlet UITextField *usernameTextField; 22 | @property (strong, nonatomic) IBOutlet UITextField *passwordTextField; 23 | @property (strong, nonatomic) IBOutlet UIButton *loginButton; 24 | 25 | - (IBAction)doLogin:(id)sender; 26 | - (IBAction)resetPassword:(id)sender; 27 | 28 | + (void)logout; 29 | @end 30 | -------------------------------------------------------------------------------- /V2EX/Controllers/V2EXUserLoginViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXUserLoginViewController.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/1/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXUserLoginViewController.h" 10 | #import 11 | #import "UIViewController+MBProgressHUD.h" 12 | #import "TFHpple+V2EXMethod.h" 13 | //#import "V2EXGlobalCache.h" 14 | 15 | @interface V2EXUserLoginViewController () 16 | 17 | @end 18 | 19 | @implementation V2EXUserLoginViewController 20 | 21 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 22 | { 23 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 24 | if (self) { 25 | // Custom initialization 26 | } 27 | return self; 28 | } 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | 34 | // Set request and response serializer 35 | _manager = [[AFHTTPSessionManager alloc] init]; 36 | _requestSerializer = [AFHTTPRequestSerializer serializer]; 37 | _responseSerializer = [AFHTTPResponseSerializer serializer]; 38 | [_requestSerializer setValue:@"http://v2ex.com/signin" forHTTPHeaderField:@"Referer"]; 39 | [_requestSerializer setValue:DEFAULT_USET_AGENT forHTTPHeaderField:@"User-Agent"]; 40 | [_responseSerializer setAcceptableContentTypes:[NSSet setWithObjects:@"text/html", nil]]; 41 | _manager.requestSerializer = _requestSerializer; 42 | _manager.responseSerializer = _responseSerializer; 43 | 44 | [self loadSavedUserData]; 45 | 46 | if (_isLoadFromSeflController) { 47 | UIBarButtonItem *btn = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"IconMenu"] style:UIBarButtonItemStylePlain target:self action:@selector(showMenu:)]; 48 | // btn.image = [UIImage imageNamed:@"IconMenu"]; 49 | 50 | self.navigationItem.leftBarButtonItem = btn; 51 | } 52 | 53 | //To hide the keyboard when empty area is touched 54 | UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] 55 | initWithTarget:self action:@selector(handleBackgroundTap:)]; 56 | tapRecognizer.cancelsTouchesInView = NO; 57 | [self.view addGestureRecognizer:tapRecognizer]; 58 | } 59 | 60 | - (void) handleBackgroundTap:(UITapGestureRecognizer*)sender 61 | { 62 | [self.usernameTextField resignFirstResponder]; 63 | [self.passwordTextField resignFirstResponder]; 64 | } 65 | 66 | - (BOOL)textFieldShouldReturn:(UITextField *)textField{ 67 | [textField resignFirstResponder]; 68 | return YES; 69 | } 70 | 71 | - (void)showMenu:(id)sender { 72 | [self.sideMenuViewController presentMenuViewController]; 73 | } 74 | 75 | - (void)loadSavedUserData { 76 | NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults]; 77 | NSString *username = [userDefaultes stringForKey:@"saved_username"]; 78 | NSString *password = [userDefaultes stringForKey:@"saved_password"]; 79 | 80 | if (username) { 81 | self.usernameTextField.text = username; 82 | if (password) { 83 | self.passwordTextField.text = password; 84 | [self doLogin:nil]; 85 | } 86 | } 87 | } 88 | 89 | - (IBAction)doLogin:(id)sender { 90 | 91 | // [_model userLoginWithUsername:self.usernameTextField.text andPassword:self.passwordTextField.text]; 92 | // [self.navigationController popViewControllerAnimated:YES]; 93 | if ([self.usernameTextField.text length] > 0 && [self.passwordTextField.text length] > 0) { 94 | [self prepareForLogin]; 95 | } else { 96 | [self showMessage:@"请输入用户名和密码"]; 97 | } 98 | 99 | } 100 | 101 | - (IBAction)resetPassword:(id)sender { 102 | //TODO: support https 103 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://v2ex.com/forgot"]]; 104 | } 105 | 106 | #pragma mark - Login selector 107 | - (void)prepareForLogin { 108 | [self showProgressView]; 109 | self.usernameTextField.enabled = NO; 110 | self.passwordTextField.enabled = NO; 111 | self.loginButton.enabled = NO; 112 | 113 | [_manager GET:@"http://v2ex.com/signin" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) { 114 | TFHpple *doc = [[TFHpple alloc]initWithHTMLData:responseObject]; 115 | 116 | NSString *next = [[doc searchFirstElementWithXPathQuery:@"//input[@name='next']"] objectForKey:@"value"]; 117 | NSString *once = [[doc searchFirstElementWithXPathQuery:@"//input[@name='once']"] objectForKey:@"value"]; 118 | NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:next, @"next", once, @"once", nil]; 119 | 120 | 121 | [self performSelector:@selector(login:) withObject:dict afterDelay:1]; 122 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 123 | [self showLoginNetworkError:error]; 124 | }]; 125 | 126 | } 127 | 128 | - (void)login:(NSDictionary *)dict { 129 | [_manager POST:@"http://v2ex.com/signin" parameters:[NSDictionary dictionaryWithObjectsAndKeys:self.usernameTextField.text, @"u", self.passwordTextField.text, @"p", 130 | [dict objectForKey:@"next"], @"next", [dict objectForKey:@"once"], @"once", nil] success:^(NSURLSessionDataTask *task, id responseObject) { 131 | // NSHTTPURLResponse *response = (NSHTTPURLResponse *)[task response]; 132 | // NSDictionary *headers = [response allHeaderFields]; 133 | 134 | // NSLog(@"%@", headers); 135 | // TODO: Handle 302 redirect 136 | if (YES/*[headers objectForKey:@"Location"]*/) { 137 | [_manager GET:@"http://v2ex.com/signin" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) { 138 | // [task response 139 | [self validateLogin:responseObject]; 140 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 141 | [self showLoginNetworkError:error]; 142 | }]; 143 | } else { 144 | [self validateLogin:responseObject]; 145 | } 146 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 147 | [self showLoginNetworkError:error]; 148 | }]; 149 | } 150 | 151 | - (void)validateLogin:(NSData *)responseObject { 152 | TFHpple *doc = [[TFHpple alloc] initWithHTMLData:responseObject]; 153 | if ([self checkLogin:doc]) { 154 | [self afterLogin]; 155 | [self.navigationController popViewControllerAnimated:YES]; 156 | [self showMessage:@"登录成功"]; 157 | } else { 158 | // NSLog(@"data:%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]); 159 | [self showLoginPasswordError]; 160 | } 161 | } 162 | 163 | - (BOOL)checkLogin:(TFHpple *)doc { 164 | NSArray *usernameArray = [doc searchWithXPathQuery:@"//div[@id='Top']//table//td[@width='auto']//a[@class='top']"]; 165 | if ([usernameArray count] > 0) { 166 | NSString *username =[[[usernameArray objectAtIndex:0] objectForKey:@"href"] stringByReplacingOccurrencesOfString:@"/member/" withString:@""]; 167 | if ([[username lowercaseString] isEqualToString:[self.usernameTextField.text lowercaseString]]) { 168 | NSLog(@"User log in: %@", username); 169 | return YES; 170 | } 171 | } 172 | return NO; 173 | } 174 | 175 | /** 176 | * After Login set timestamp to "/user/lastlogin" in Global Cache 177 | * 178 | * @param username current username 179 | */ 180 | - (void)afterLogin { 181 | [self saveUsernameAndPassword]; 182 | [self finishLoginRequest]; 183 | } 184 | 185 | /** 186 | * Logout - Just clean the cookies 187 | */ 188 | + (void)logout { 189 | NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 190 | for (NSHTTPCookie *cookie in [storage cookies]) { 191 | [storage deleteCookie:cookie]; 192 | } 193 | } 194 | 195 | - (void)finishLoginRequest { 196 | [self hideProgressView]; 197 | self.usernameTextField.enabled = YES; 198 | self.passwordTextField.enabled = YES; 199 | self.loginButton.enabled = YES; 200 | } 201 | 202 | // Handle error 203 | - (void)showLoginNetworkError:(NSError *)error { 204 | [self finishLoginRequest]; 205 | [self showMessage:@"登录过程中出现网络错误,请重试"]; 206 | 207 | NSLog(@"[ERROR]Network Error during login action: %@", [error description]); 208 | } 209 | 210 | - (void)showLoginOtherError:(NSError *)error { 211 | [self finishLoginRequest]; 212 | [self showMessage:@"无法检测您是否已经登录,请重试"]; 213 | [V2EXUserLoginViewController logout]; 214 | NSLog(@"[ERROR]Other Error(might about HTML DOM) during login action: %@", [error description]); 215 | } 216 | 217 | - (void)showLoginPasswordError { 218 | [self finishLoginRequest]; 219 | [self showMessage:@"登录失败,用户名或密码错误"]; 220 | [V2EXUserLoginViewController logout]; 221 | } 222 | 223 | // Save username and password 224 | 225 | - (void)saveUsernameAndPassword { 226 | NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults]; 227 | [userDefaultes setObject:self.usernameTextField.text forKey:@"saved_username"]; 228 | [userDefaultes setObject:self.passwordTextField.text forKey:@"saved_password"]; 229 | [userDefaultes synchronize]; 230 | } 231 | 232 | 233 | @end 234 | -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Small.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-Small@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-40@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "57x57", 23 | "idiom" : "iphone", 24 | "filename" : "Icon.png", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "size" : "57x57", 29 | "idiom" : "iphone", 30 | "filename" : "Icon@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "Icon-Small-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "Icon-Small@2x-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-40.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-40@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "50x50", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-Small-50.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "50x50", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-Small-50@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "72x72", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-72.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "72x72", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-72@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-76@2x.png", 97 | "scale" : "2x" 98 | } 99 | ], 100 | "info" : { 101 | "version" : 1, 102 | "author" : "xcode" 103 | } 104 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconBack.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconBack@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconBack.imageset/IconBack@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconBack.imageset/IconBack@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconChatting.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "chatting.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconChatting.imageset/chatting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconChatting.imageset/chatting.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconEmpty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconEmpty@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconEmpty.imageset/IconEmpty@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconEmpty.imageset/IconEmpty@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconHome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconHome@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconHome.imageset/IconHome@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconHome.imageset/IconHome@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconKey.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconKey@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconKey.imageset/IconKey@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconKey.imageset/IconKey@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconMenu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "icon_menu.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "icon_menu@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconMenu.imageset/icon_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconMenu.imageset/icon_menu.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconMenu.imageset/icon_menu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconMenu.imageset/icon_menu@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconProfile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconProfile@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconProfile.imageset/IconProfile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconProfile.imageset/IconProfile@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconRecent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconRecent@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconRecent.imageset/IconRecent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconRecent.imageset/IconRecent@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconRecent.imageset/timer-vector.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconRecent.imageset/timer-vector.psd -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconReply.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "speech-bubble-vector.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconReply.imageset/speech-bubble-vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconReply.imageset/speech-bubble-vector.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconSettings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "IconSettings@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/IconSettings.imageset/IconSettings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/IconSettings.imageset/IconSettings@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "filename" : "Default@640x960.png", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "retina4", 15 | "filename" : "Default@640x1136-1.png", 16 | "minimum-system-version" : "7.0", 17 | "orientation" : "portrait", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "ipad", 23 | "extent" : "full-screen", 24 | "minimum-system-version" : "7.0", 25 | "filename" : "Default@768x1024.png", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "orientation" : "portrait", 30 | "idiom" : "ipad", 31 | "extent" : "full-screen", 32 | "minimum-system-version" : "7.0", 33 | "filename" : "Default@1536x2048.png", 34 | "scale" : "2x" 35 | }, 36 | { 37 | "orientation" : "portrait", 38 | "idiom" : "ipad", 39 | "extent" : "full-screen", 40 | "filename" : "Default@768x1024-1.png", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "orientation" : "portrait", 45 | "idiom" : "ipad", 46 | "extent" : "full-screen", 47 | "filename" : "Default@1536x2048-1.png", 48 | "scale" : "2x" 49 | } 50 | ], 51 | "info" : { 52 | "version" : 1, 53 | "author" : "xcode" 54 | } 55 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@1536x2048-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@1536x2048-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@1536x2048.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@640x1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@640x1136-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@640x960.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@768x1024-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@768x1024-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage.launchimage/Default@768x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage.launchimage/Default@768x1024.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "filename" : "Default@640x960-1.png", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "retina4", 15 | "filename" : "Default@640x1136-1.png", 16 | "minimum-system-version" : "7.0", 17 | "orientation" : "portrait", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "ipad", 23 | "extent" : "full-screen", 24 | "minimum-system-version" : "7.0", 25 | "filename" : "Default@768x1024-1.png", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "orientation" : "landscape", 30 | "idiom" : "ipad", 31 | "extent" : "full-screen", 32 | "minimum-system-version" : "7.0", 33 | "filename" : "Default@1024x768-1.png", 34 | "scale" : "1x" 35 | }, 36 | { 37 | "orientation" : "portrait", 38 | "idiom" : "ipad", 39 | "extent" : "full-screen", 40 | "minimum-system-version" : "7.0", 41 | "filename" : "Default@1536x2048-1.png", 42 | "scale" : "2x" 43 | }, 44 | { 45 | "orientation" : "landscape", 46 | "idiom" : "ipad", 47 | "extent" : "full-screen", 48 | "minimum-system-version" : "7.0", 49 | "filename" : "Default@2048x1536-1.png", 50 | "scale" : "2x" 51 | }, 52 | { 53 | "orientation" : "portrait", 54 | "idiom" : "iphone", 55 | "extent" : "full-screen", 56 | "filename" : "Default@320x480.png", 57 | "scale" : "1x" 58 | }, 59 | { 60 | "orientation" : "portrait", 61 | "idiom" : "iphone", 62 | "extent" : "full-screen", 63 | "filename" : "Default@640x960.png", 64 | "scale" : "2x" 65 | }, 66 | { 67 | "orientation" : "portrait", 68 | "idiom" : "iphone", 69 | "extent" : "full-screen", 70 | "filename" : "Default@640x1136.png", 71 | "subtype" : "retina4", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "orientation" : "portrait", 76 | "idiom" : "ipad", 77 | "extent" : "to-status-bar", 78 | "filename" : "Default@768x1004.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "orientation" : "portrait", 83 | "idiom" : "ipad", 84 | "extent" : "full-screen", 85 | "filename" : "Default@768x1024.png", 86 | "scale" : "1x" 87 | }, 88 | { 89 | "orientation" : "landscape", 90 | "idiom" : "ipad", 91 | "extent" : "to-status-bar", 92 | "filename" : "Default@1024x748.png", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "orientation" : "landscape", 97 | "idiom" : "ipad", 98 | "extent" : "full-screen", 99 | "filename" : "Default@1024x768.png", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "orientation" : "portrait", 104 | "idiom" : "ipad", 105 | "extent" : "to-status-bar", 106 | "filename" : "Default@1536x2008.png", 107 | "scale" : "2x" 108 | }, 109 | { 110 | "orientation" : "portrait", 111 | "idiom" : "ipad", 112 | "extent" : "full-screen", 113 | "filename" : "Default@1536x2048.png", 114 | "scale" : "2x" 115 | }, 116 | { 117 | "orientation" : "landscape", 118 | "idiom" : "ipad", 119 | "extent" : "to-status-bar", 120 | "filename" : "Default@2048x1496.png", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "orientation" : "landscape", 125 | "idiom" : "ipad", 126 | "extent" : "full-screen", 127 | "filename" : "Default@2048x1536.png", 128 | "scale" : "2x" 129 | } 130 | ], 131 | "info" : { 132 | "version" : 1, 133 | "author" : "xcode" 134 | } 135 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x748.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x748.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x768-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x768-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1024x768.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2008.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2048-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2048-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@1536x2048.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1496.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1536-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1536-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@2048x1536.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@320x480.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x1136-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x1136.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x960-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x960-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@640x960.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1004.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1024-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1024-1.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/LaunchImage2.launchimage/Default@768x1024.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/Stars.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Stars@2x.png" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/Stars.imageset/Stars@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/Stars.imageset/Stars@2x.png -------------------------------------------------------------------------------- /V2EX/Images.xcassets/avatar_large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "avatar_large.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /V2EX/Images.xcassets/avatar_large.imageset/avatar_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imWildCat/V2EX-iOS-ObjC/ce2022e087f8df8c99d102f001955bc286fd182e/V2EX/Images.xcassets/avatar_large.imageset/avatar_large.png -------------------------------------------------------------------------------- /V2EX/Models/Data/NodesList/NodesList.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | qna 7 | share 8 | create 9 | autistic 10 | in 11 | random 12 | ideas 13 | design 14 | blog 15 | paper 16 | 17 | 18 | v2ex 19 | babel 20 | gae 21 | dns 22 | feedback 23 | picky 24 | guide 25 | olivida 26 | 27 | 28 | idev 29 | imarketing 30 | iad 31 | icode 32 | itransfer 33 | 34 | 35 | programmer 36 | python 37 | android 38 | linux 39 | cloud 40 | bitcoin 41 | php 42 | server 43 | hardware 44 | linode 45 | designer 46 | outsourcing 47 | kindle 48 | mysql 49 | bb 50 | tornado 51 | ror 52 | typography 53 | java 54 | markdown 55 | programming 56 | mongodb 57 | redis 58 | ruby 59 | car 60 | business 61 | photoshop 62 | lego 63 | math 64 | sony 65 | 66 | 67 | games 68 | igame 69 | bf3 70 | lol 71 | sc2 72 | steam 73 | ps3 74 | wow 75 | eve 76 | xbox360 77 | gt 78 | wii 79 | 80 | 81 | macosx 82 | iphone 83 | mbp 84 | ipad 85 | mba 86 | accessory 87 | imac 88 | macmini 89 | ipod 90 | macbook 91 | mobileme 92 | macpro 93 | iwork 94 | ilife 95 | garageband 96 | 97 | 98 | all4all 99 | jobs 100 | afterdark 101 | music 102 | movie 103 | exchange 104 | reading 105 | photograph 106 | tv 107 | taste 108 | creditcard 109 | invest 110 | travel 111 | lohas 112 | baby 113 | soccer 114 | pet 115 | bike 116 | tuan 117 | coffee 118 | plant 119 | diary 120 | love 121 | mushroom 122 | mileage 123 | 124 | 125 | google 126 | twitter 127 | facebook 128 | wikipedia 129 | path 130 | reddit 131 | 4sq 132 | 133 | 134 | beijing 135 | shanghai 136 | shenzhen 137 | hangzhou 138 | guangzhou 139 | chengdu 140 | kunming 141 | wuhan 142 | nyc 143 | tianjin 144 | la 145 | sanfrancisco 146 | 147 | 148 | uniqlo 149 | lamy 150 | ikea 151 | muji 152 | gap 153 | moleskine 154 | gstar 155 | nike 156 | adidas 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /V2EX/Models/Data/repliesList.css: -------------------------------------------------------------------------------- 1 | .dock_area { 2 | background-color: #EDF3F5; 3 | background-repeat: repeat-x; 4 | padding: 0; 5 | } 6 | .fade { 7 | color: #CCCCCC; 8 | } 9 | .fr { 10 | text-align: right; 11 | } 12 | .gray { 13 | color: #999999; 14 | } 15 | .chevron { 16 | font-family: "Lucida Grande"; 17 | font-weight: 500; 18 | } 19 | a:link, a:visited, a:active { 20 | font-size:14px; 21 | color: #778087; 22 | text-decoration: none; 23 | } 24 | a { 25 | font-size:14px; 26 | color: #778087; 27 | text-decoration: none; 28 | } 29 | div { 30 | font-size:15px; 31 | } -------------------------------------------------------------------------------- /V2EX/Models/V2EXAbstractModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAbstractModel.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/3/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "V2EXApiClient.h" 11 | #import "V2EXRequestDataDelegate.h" 12 | 13 | @interface V2EXAbstractModel : NSObject 14 | { 15 | id _delegate; 16 | }; 17 | 18 | 19 | @property (nonatomic, assign) V2EXApiClient *apiClient; 20 | 21 | - (id) initWithDelegate:(id)delegate; 22 | - (NSDictionary *) parseData:(id)dataObject; 23 | - (void)loadDataSuccess:(id)dataObject; 24 | - (void)loadDataFailure:(NSError *)error; 25 | // Need to support request start and terminate request. For example, - (void)startLoadData & - (void)terminateLoading; 26 | // If not, 27 | 28 | 29 | //- (void)loadData:(NSString *)uri isGetMethod:(BOOL)isGetMethod isJsonApi:(BOOL)isJsonApi parameters:(NSDictionary *)parameters; 30 | // Base Data 31 | - (void)getJSONData:(NSString *)uri parameters:(NSDictionary *)parameter; 32 | 33 | // HTML Data 34 | - (void)getHTMLData:(NSString *)uri parameters:(NSDictionary *)parameter; 35 | 36 | - (void)postData:(NSString *)uri parameters:(NSDictionary *)parameter; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXAbstractModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAbstractModel.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/3/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXAbstractModel.h" 10 | #import "V2EXMBProgressHUDUtil.h" 11 | 12 | @implementation V2EXAbstractModel 13 | 14 | - (id)init { 15 | [NSException raise:@"V2EXAbstractModel Exception" format:@"can't use init method"]; 16 | return self; 17 | } 18 | 19 | - (id)initWithDelegate:(id )delegate { 20 | self = [super init]; 21 | if(self){ 22 | _delegate = delegate; 23 | self.apiClient = [V2EXApiClient sharedClient]; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)loadData:(NSString *)uri isGetMethod:(BOOL)isGetMethod isJsonApi:(BOOL)isJsonApi parameters:(NSDictionary *)parameters { 29 | [self.apiClient managerRequestData:uri isGetMethod:isGetMethod isJsonApi:isJsonApi parameters:parameters success:^(id dataObject) { 30 | [self loadDataSuccess:[self parseData:dataObject]]; 31 | } failure:^(NSError *error) { 32 | [self loadDataFailure:error]; 33 | }]; 34 | 35 | } 36 | 37 | - (NSDictionary *) parseData:(id)dataObject { 38 | //Optional to be overwritten. 39 | return dataObject; 40 | } 41 | 42 | - (void)loadDataSuccess:(id)dataObject { 43 | [V2EXMBProgressHUDUtil dismissGlobalHUD]; 44 | [_delegate requestDataSuccess:dataObject]; 45 | } 46 | 47 | - (void)loadDataFailure:(NSError *)error { 48 | NSString *errorMessage; 49 | switch ([error code]) { 50 | case 444: 51 | errorMessage = @"抱歉,请等待上一次请求完成"; 52 | break; 53 | case -1009: 54 | errorMessage = @"加载失败,似乎没有连接到网络"; 55 | break; 56 | default: 57 | errorMessage = @"网络错误,加载失败"; 58 | break; 59 | } 60 | [V2EXMBProgressHUDUtil dismissGlobalHUD]; 61 | 62 | [V2EXMBProgressHUDUtil showMessage:errorMessage]; 63 | if ([_delegate respondsToSelector:@selector(requestDataFailure:)]) { 64 | [_delegate requestDataFailure:errorMessage]; 65 | } 66 | 67 | NSLog(@"[ERROR] Network: %@", [error description]); 68 | } 69 | 70 | 71 | // Base Data 72 | - (void)getJSONData:(NSString *)uri parameters:(NSDictionary *)parameter { 73 | [self loadData:uri isGetMethod:YES isJsonApi:YES parameters:parameter]; 74 | } 75 | 76 | // HTML Data 77 | - (void)getHTMLData:(NSString *)uri parameters:(NSDictionary *)parameter { 78 | [self loadData:uri isGetMethod:YES isJsonApi:NO parameters:parameter]; 79 | } 80 | 81 | - (void)postData:(NSString *)uri parameters:(NSDictionary *)parameter { 82 | // NSMutableString *uriWithParam = [uri mutableCopy]; 83 | // if ([parameter count] > 0) { 84 | // [uriWithParam appendString:@"?"]; 85 | // BOOL isFirst = YES; 86 | // 87 | // for (NSString *key in [parameter allKeys] ) { 88 | // NSString *value = [parameter objectForKey:key]; 89 | // 90 | // if (!isFirst) { 91 | // [uriWithParam appendString:@"&"]; 92 | // } 93 | // 94 | // [uriWithParam appendString:[NSString stringWithFormat:@"%@=%@", key, value]]; 95 | // } 96 | // } 97 | 98 | // [self loadData:uriWithParam isGetMethod:NO isJsonApi:NO parameters:nil]; 99 | [self loadData:uri isGetMethod:NO isJsonApi:NO parameters:parameter]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXNodesListModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNodesListModel.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/14/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "V2EXRequestDataDelegate.h" 12 | 13 | @interface V2EXNodesListModel : NSObject 14 | { 15 | NSArray *_nodeURIs; 16 | NSMutableArray *_nodeTitles; 17 | NSMutableArray *_nodeHeaders; 18 | } 19 | 20 | @property(nonatomic, assign) id delegate; 21 | 22 | - (id)initWithIndex:(NSUInteger)index; 23 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; 24 | @end 25 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXNodesListModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNodesListModel.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/14/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXNodesListModel.h" 10 | #import "V2EXNodesListCell.h" 11 | 12 | @implementation V2EXNodesListModel 13 | 14 | - (id)initWithIndex:(NSUInteger)idx { 15 | 16 | if ([super init]) { 17 | NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"NodesList" ofType:@"plist"]; 18 | NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath]; 19 | _nodeURIs = [array objectAtIndex:idx]; 20 | 21 | _nodeTitles = [[NSMutableArray alloc] init]; 22 | _nodeHeaders = [[NSMutableArray alloc] init]; 23 | 24 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 25 | NSString *documentDirectory = [paths objectAtIndex:0]; 26 | NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@"db/v2ex_normal.db"]; 27 | FMDatabase *db = [FMDatabase databaseWithPath:dbPath]; 28 | 29 | [db open]; 30 | for (NSString *nodeURI in _nodeURIs) { 31 | FMResultSet *retSet = [db executeQuery:@"SELECT * FROM nodes WHERE uri = ?",nodeURI]; 32 | [retSet next]; 33 | NSString *title = [retSet stringForColumn:@"title"]; 34 | NSString *header = [retSet stringForColumn:@"header"]; 35 | [_nodeTitles addObject:title]; 36 | if (header) { 37 | [_nodeHeaders addObject:header]; 38 | } else { 39 | [_nodeHeaders addObject:@""]; 40 | } 41 | } 42 | [db close]; 43 | } 44 | return self; 45 | } 46 | 47 | #pragma mark - Table view data source 48 | 49 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 50 | { 51 | return 1; 52 | } 53 | 54 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 55 | { 56 | return [_nodeTitles count]; 57 | } 58 | 59 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 60 | { 61 | static NSString *CellIdentifier = @"nodesListCell"; 62 | // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 63 | // if (cell == nil) { 64 | // cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier]; 65 | // } else { 66 | // while ([cell.contentView.subviews lastObject] != nil) { 67 | // [(UIView*)[cell.contentView.subviews lastObject]removeFromSuperview]; 68 | // } 69 | // } 70 | 71 | UINib *nib = [UINib nibWithNibName:@"V2EXNodesListCell" bundle:nil]; 72 | [tableView registerNib:nib forCellReuseIdentifier:CellIdentifier]; 73 | V2EXNodesListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 74 | cell.delegate = self.delegate; 75 | 76 | NSUInteger index = [indexPath row]; 77 | NSString *uri = [_nodeURIs objectAtIndex:index]; 78 | 79 | cell.nodeTitle.text = [_nodeTitles objectAtIndex:index]; 80 | cell.nodeHeader.text = [_nodeHeaders objectAtIndex:index]; 81 | cell.nodeURI = uri; 82 | 83 | return cell; 84 | } 85 | 86 | #pragma mark - UITableViewDelegate 87 | // TODO: Find the better scheme for page view because this method don't working while UITableView in UISrollView 88 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 89 | NSUInteger index = indexPath.row; 90 | NSLog(@"%i",index); 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXNormalModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNormalModel.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/8/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXAbstractModel.h" 10 | 11 | @interface V2EXNormalModel : V2EXAbstractModel 12 | 13 | // API 14 | - (void)getIndex; 15 | - (void)getTopicsList:(NSString *)URI; 16 | //- (void)getTopicWithLinkURI:(NSString *)URI; 17 | 18 | 19 | // User 20 | - (void)getUserInfo:(NSString *)username; 21 | - (void)getUserTopics:(NSString *)username; 22 | - (void)getUserReplies:(NSString *)username; 23 | 24 | // JSON API 25 | - (void)getAllNodes; 26 | - (void)getLatestTopics; 27 | 28 | // Topic 29 | - (void)getTopicWithID:(NSUInteger)ID; 30 | - (void)replyTopic:(NSUInteger)topicID andOnce:(NSUInteger)onceCode andContent:(NSString *)content; 31 | - (void)getNewTopicPage:(NSString *)uri; 32 | - (void)newTopic:(NSString *)uri andTitle:(NSString *)title andContent:(NSString *)content andOnce:(NSUInteger)once; 33 | - (void)reportTopic:(NSUInteger) topicID; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXNormalModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNormalModel.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/8/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXNormalModel.h" 10 | #import "JSONAPIURI.h" 11 | 12 | @implementation V2EXNormalModel 13 | 14 | - (void)getIndex { 15 | [self getHTMLData:@"" parameters:nil]; 16 | } 17 | 18 | - (void)getTopicsList:(NSString *)URI { 19 | [self getHTMLData:[@"go/" stringByAppendingString:URI] parameters:nil]; 20 | } 21 | 22 | //- (void)getTopicWithLinkURI:(NSString *)URI { 23 | // [self getHTMLData:[URI stringByReplacingOccurrencesOfString:@"/t/" withString:@"t/"] parameters:nil]; 24 | //} 25 | 26 | 27 | // User 28 | - (void)getUserInfo:(NSString *)username { 29 | [self getHTMLData:[NSString stringWithFormat:@"member/%@", username] parameters:nil]; 30 | } 31 | 32 | - (void)getUserTopics:(NSString *)username { 33 | [self getHTMLData:[NSString stringWithFormat:@"member/%@/topics", username] parameters:nil]; 34 | } 35 | 36 | - (void)getUserReplies:(NSString *)username { 37 | [self getHTMLData:[NSString stringWithFormat:@"member/%@/replies", username] parameters:nil]; 38 | } 39 | 40 | 41 | // JSON API 42 | - (void)getLatestTopics { 43 | [self getJSONData:TOPICS_LATEST parameters:nil]; 44 | } 45 | 46 | - (void)getAllNodes { 47 | [self getJSONData:NODES_ALL parameters:nil]; 48 | } 49 | 50 | // Topic 51 | - (void)getTopicWithID:(NSUInteger)ID { 52 | [self getHTMLData:[NSString stringWithFormat:@"t/%i", (unsigned int)ID] parameters:nil]; 53 | } 54 | 55 | - (void)replyTopic:(NSUInteger)topicID andOnce:(NSUInteger)onceCode andContent:(NSString *)content { 56 | [self postData:[NSString stringWithFormat:@"t/%i", topicID] parameters:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%i", onceCode], @"once", content, @"content", nil]]; 57 | } 58 | 59 | - (void)getNewTopicPage:(NSString *)uri { 60 | [self getHTMLData:[NSString stringWithFormat:@"new/%@", uri] parameters:nil]; 61 | } 62 | 63 | - (void)newTopic:(NSString *)uri andTitle:(NSString *)title andContent:(NSString *)content andOnce:(NSUInteger)once { 64 | [self postData:[NSString stringWithFormat:@"new/%@", uri] parameters:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%i", once], @"once", title, @"title", content, @"content", nil]]; 65 | } 66 | 67 | - (void)reportTopic:(NSUInteger)topicID { 68 | NSUInteger time = [[NSDate date] timeIntervalSince1970]; 69 | [self getHTMLData:[NSString stringWithFormat:@"report/topic/%i?t=%i", topicID, time] parameters:nil]; 70 | } 71 | 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /V2EX/Models/V2EXRequestDataDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXRequestDataDelegate.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/3/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol V2EXRequestDataDelegate 12 | 13 | @required 14 | 15 | - (void)requestDataSuccess:(id)dataObject; 16 | 17 | @optional 18 | - (void)requestDataFailure:(NSString *)errorMessage; 19 | - (void)requestTopicsList:(NSString *)nodeTitle andURI:(NSString *)URI; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /V2EX/ThirdParties/DRPaginatedScrollView/DRPaginatedScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRPaginatedScrollView.h 3 | // Proday 4 | // 5 | // Created by David Román Aguirre on 24/12/13. 6 | // Copyright (c) 2013 David Román Aguirre. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DRPaginatedScrollView : UIScrollView 12 | 13 | @property (nonatomic) NSTimeInterval jumpDurationPerPage; 14 | @property (readonly, nonatomic, getter = isJumping) BOOL jumping; 15 | 16 | @property (copy, nonatomic) void (^actionWhenTappedBlock)(DRPaginatedScrollView *); 17 | 18 | - (NSInteger)currentPage; 19 | - (NSInteger)nextPage; 20 | - (NSInteger)lastPage; 21 | - (NSInteger)numberOfPages; 22 | 23 | - (void)addPageWithHandler:(void (^)(UIView * pageView))handler; 24 | - (void)jumpToPage:(NSInteger)page bounce:(CGFloat)bounce completion:(void (^)(void))completion; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /V2EX/ThirdParties/DRPaginatedScrollView/DRPaginatedScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRPaginatedScrollView.m 3 | // Proday 4 | // 5 | // Created by David Román Aguirre on 24/12/13. 6 | // Copyright (c) 2013 David Román Aguirre. All rights reserved. 7 | // 8 | 9 | #import "DRPaginatedScrollView.h" 10 | 11 | @interface DRPaginatedScrollView () { 12 | NSInteger previousPage; 13 | NSMutableArray * pageViews; 14 | } 15 | 16 | @property (strong, nonatomic) UITapGestureRecognizer * tapGestureRecognizer; 17 | 18 | @property (readwrite, nonatomic, getter = isJumping) BOOL jumping; 19 | 20 | @end 21 | 22 | @implementation DRPaginatedScrollView 23 | 24 | - (id)init { 25 | if (self = [super init]) { 26 | pageViews = [NSMutableArray new]; 27 | 28 | [self setPagingEnabled:YES]; 29 | [self setShowsHorizontalScrollIndicator:NO]; 30 | [self setShowsVerticalScrollIndicator:NO]; 31 | [self setJumpDurationPerPage:0.1]; 32 | 33 | // [self setActionWhenTappedBlock:^(DRPaginatedScrollView * paginatedScrollView) { 34 | // [paginatedScrollView jumpToPage:[paginatedScrollView nextPage] bounce:0 completion:nil]; 35 | // }]; 36 | 37 | self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap)]; 38 | [self addGestureRecognizer:self.tapGestureRecognizer]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)handleTap { 45 | if (self.actionWhenTappedBlock) self.actionWhenTappedBlock(self); 46 | } 47 | 48 | - (void)layoutSubviews { 49 | if (self.contentSize.width != self.frame.size.width*self.numberOfPages) { 50 | [self setContentSize:CGSizeMake(self.frame.size.width*self.numberOfPages, self.contentSize.height)]; 51 | [self setContentOffset:CGPointMake(self.frame.size.width*previousPage, self.contentOffset.y)]; 52 | } else { 53 | previousPage = [self currentPage]; 54 | } 55 | 56 | [super layoutSubviews]; 57 | } 58 | 59 | - (NSInteger)currentPage { 60 | return round(self.contentOffset.x/self.frame.size.width); 61 | } 62 | 63 | - (NSInteger)nextPage { 64 | return self.currentPage+1; 65 | } 66 | 67 | - (NSInteger)lastPage { 68 | return self.numberOfPages-1; 69 | } 70 | 71 | - (NSInteger)numberOfPages { 72 | return [pageViews count]; 73 | } 74 | 75 | - (void)addPageWithHandler:(void (^)(UIView * pageView))handler { 76 | UIView * pageView = [UIView new]; 77 | 78 | handler(pageView); 79 | [pageView setBackgroundColor:[UIColor clearColor]]; 80 | [pageView setTranslatesAutoresizingMaskIntoConstraints:NO]; 81 | [self addSubview:pageView]; 82 | 83 | UIView * previousPageView; 84 | 85 | if (self.numberOfPages > 0) { 86 | previousPageView = pageViews[self.numberOfPages-1]; 87 | } 88 | 89 | NSLayoutConstraint * topConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 90 | NSLayoutConstraint * leftConstraint; 91 | NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; 92 | NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1 constant:0]; 93 | 94 | if (previousPageView) { 95 | leftConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousPageView attribute:NSLayoutAttributeRight multiplier:1 constant:0]; 96 | } else { 97 | leftConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; 98 | } 99 | 100 | [self addConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; 101 | 102 | [pageViews addObject:pageView]; 103 | } 104 | 105 | - (void)jumpToPage:(NSInteger)page bounce:(CGFloat)bounce completion:(void (^)(void))completion { 106 | if (!self.isJumping && page < [self numberOfPages]) { 107 | [self setJumping:YES]; 108 | 109 | [self setPagingEnabled:NO]; 110 | if (self.frame.size.width*page < self.contentOffset.x) bounce = -bounce; 111 | 112 | [UIView animateWithDuration:self.jumpDurationPerPage*[self numberOfPages] animations:^{ 113 | [self setContentOffset:CGPointMake(self.frame.size.width*page+bounce, self.contentOffset.y)]; 114 | } completion:^(BOOL finished) { 115 | [UIView animateWithDuration:self.jumpDurationPerPage*[self numberOfPages]/2.75 animations:^{ 116 | [self setContentOffset:CGPointMake(self.contentOffset.x-bounce, self.contentOffset.y)]; 117 | } completion:^(BOOL finished) { 118 | [self setJumping:NO]; 119 | [self setPagingEnabled:YES]; 120 | if (completion) completion(); 121 | }]; 122 | }]; 123 | } 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/JSONAPIURI.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONAPIURI.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/8/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #ifndef V2EX_JSONAPIURI_h 10 | #define V2EX_JSONAPIURI_h 11 | 12 | #define SITE_STATS @"site/stats.json" 13 | #define SITE_INFO @"site/info.json" 14 | #define NODES_ALL @"nodes/all.json" 15 | #define NODES_SHOW @"nodes/show.json" 16 | #define TOPICS_LATEST @"topics/latest.json" 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/NormalAPIURI.h: -------------------------------------------------------------------------------- 1 | // 2 | // NormalAPIURI.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/9/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #ifndef V2EX_NormalAPIURI_h 10 | #define V2EX_NormalAPIURI_h 11 | 12 | 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/V2EXApiClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXApiClient.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/31/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #define DEFAULT_USET_AGENT @"5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16" 12 | 13 | @interface V2EXApiClient : NSObject 14 | { 15 | //Using AFHTTPSessionManager temporarily to support cookies 16 | AFHTTPSessionManager *_manager; 17 | 18 | BOOL _isHTTPS; 19 | 20 | BOOL _isLoading; 21 | } 22 | + (V2EXApiClient *)sharedClient; 23 | 24 | - (void) managerRequestData:(NSString *)uri isGetMethod:(BOOL)isGetMethod isJsonApi:(BOOL)isJsonApi parameters:(NSDictionary *)params success:(void (^)(id dataObject))success failure:(void (^)(NSError *error))failure; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/V2EXApiClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXApiClient.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/31/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXApiClient.h" 10 | #import "V2EXHttpRequestSerializer.h" 11 | 12 | #define HTTP_ROOT_URL @"http://www.v2ex.com/" 13 | #define HTTPS_ROOT_URL @"https://www.v2ex.com/" 14 | #define JSONAPI_URI @"api/" 15 | //TODO: Adding more support for user agent. 16 | 17 | 18 | @implementation V2EXApiClient 19 | 20 | - (id)init 21 | { 22 | self = [super init]; 23 | _manager = [AFHTTPSessionManager manager]; 24 | _isHTTPS = NO; 25 | 26 | return self; 27 | } 28 | 29 | + (V2EXApiClient *)sharedClient 30 | { 31 | static V2EXApiClient *_sharedClientInstance = nil; 32 | static dispatch_once_t predicate; 33 | dispatch_once(&predicate, ^{ 34 | _sharedClientInstance = [[self alloc] init]; 35 | 36 | }); 37 | 38 | return _sharedClientInstance; 39 | } 40 | 41 | /** 42 | * Sets _manager.requestSerializer and _manager setResponseSerializer according to (BOOL)isJsonApi. 43 | * 44 | * @param isJsonApi If using V2EX JSON API. 45 | */ 46 | - (void) _setSerializer:(BOOL)isJsonApi{ 47 | 48 | if(isJsonApi){ 49 | V2EXHttpRequestSerializer *requestSerializer = [V2EXHttpRequestSerializer serializer]; 50 | [requestSerializer setValue:DEFAULT_USET_AGENT forHTTPHeaderField:@"User-Agent"]; 51 | AFJSONResponseSerializer *responseSerializer = [AFJSONResponseSerializer serializer]; 52 | 53 | [_manager setRequestSerializer:requestSerializer]; 54 | [_manager setResponseSerializer:responseSerializer]; 55 | } else { 56 | V2EXHttpRequestSerializer *requestSerializer = [V2EXHttpRequestSerializer serializer]; 57 | [requestSerializer setValue:DEFAULT_USET_AGENT forHTTPHeaderField:@"User-Agent"]; 58 | AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; 59 | [responseSerializer setAcceptableContentTypes:[NSSet setWithObjects:@"text/html", nil]]; 60 | 61 | [_manager setRequestSerializer:requestSerializer]; 62 | [_manager setResponseSerializer:responseSerializer]; 63 | } 64 | } 65 | 66 | /** 67 | * Returns V2EX base http or https url according to (BOOL)_isHTTPS. 68 | * 69 | * @return HTTP_ROOT_URL or HTTP_ROOT_URL. 70 | */ 71 | - (NSString *) _getBaseUrl:(BOOL)isJsonApi{ 72 | NSString *rootUrl; 73 | if(_isHTTPS){ 74 | rootUrl = HTTPS_ROOT_URL; 75 | } else { 76 | rootUrl = HTTP_ROOT_URL; 77 | } 78 | if (isJsonApi) { 79 | return [rootUrl stringByAppendingString:JSONAPI_URI]; 80 | } else { 81 | return rootUrl; 82 | } 83 | } 84 | 85 | - (void)managerRequestData:(NSString *)uri isGetMethod:(BOOL)isGetMethod isJsonApi:(BOOL)isJsonApi parameters:(NSDictionary *)params success:(void (^)(id dataObject))success failure:(void (^)(NSError *error))failure 86 | { 87 | [self _setSerializer:isJsonApi]; 88 | NSString *url = [[self _getBaseUrl:isJsonApi] stringByAppendingString:uri]; 89 | if (!_isLoading) { 90 | _isLoading = YES; 91 | if(isGetMethod){ 92 | [_manager GET:url parameters:params success:^(NSURLSessionDataTask *task, id responseObject) { 93 | _isLoading = NO; 94 | success(responseObject); 95 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 96 | _isLoading = NO; 97 | failure(error); 98 | }]; 99 | } else { 100 | [_manager POST:url parameters:params success:^(NSURLSessionDataTask *task, id responseObject) { 101 | _isLoading = NO; 102 | success(responseObject); 103 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 104 | _isLoading = NO; 105 | failure(error); 106 | }]; 107 | } 108 | } else { 109 | NSError *error = [[NSError alloc] initWithDomain:@"V2EX" code:444 userInfo:nil]; 110 | failure(error); 111 | } 112 | 113 | } 114 | 115 | 116 | - (void) _managerGetJson:(NSString *)uri parameters:(NSDictionary *)params success:(void (^)(NSDictionary *jsonData))success failure:(void (^)(NSString *errorMessage))failure 117 | { 118 | [self managerRequestData:uri isGetMethod:YES isJsonApi:YES parameters:nil success:^(id data) { 119 | success(data); 120 | } failure:^(NSError *error) { 121 | failure([error description]); 122 | }]; 123 | } 124 | 125 | 126 | 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/V2EXHttpRequestSerializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXHttpRequestSerializer.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/7/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "AFURLRequestSerialization.h" 10 | 11 | @interface V2EXHttpRequestSerializer : AFHTTPRequestSerializer 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /V2EX/Utilities/Network/V2EXHttpRequestSerializer.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXHttpRequestSerializer.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/7/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXHttpRequestSerializer.h" 10 | 11 | #define TIMEOUT 30 12 | 13 | @implementation V2EXHttpRequestSerializer 14 | 15 | - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters error:(NSError *__autoreleasing *)error 16 | { 17 | NSMutableURLRequest *request = [super requestWithMethod:method URLString:URLString parameters:parameters error:error]; 18 | NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; 19 | NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; 20 | [request setAllHTTPHeaderFields:headers]; 21 | [request setTimeoutInterval:TIMEOUT]; 22 | [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; 23 | return request; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /V2EX/Utilities/TFHpple+V2EXMethod.h: -------------------------------------------------------------------------------- 1 | // 2 | // TFHpple+V2EXMethod.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/6/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "TFHpple.h" 10 | #import "TFHppleElement+V2EXMethod.h" 11 | 12 | @interface TFHpple (V2EXMethod) 13 | 14 | - (TFHppleElement *)searchFirstElementWithXPathQuery:(NSString *)query; 15 | - (BOOL)checkLogin; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Utilities/TFHpple+V2EXMethod.m: -------------------------------------------------------------------------------- 1 | // 2 | // TFHpple+V2EXMethod.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/6/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "TFHpple+V2EXMethod.h" 10 | 11 | @implementation TFHpple (V2EXMethod) 12 | 13 | - (TFHppleElement *)searchFirstElementWithXPathQuery:(NSString *)query { 14 | NSArray *retArray = [self searchWithXPathQuery:query]; 15 | if ([retArray count] > 0) { 16 | return [retArray objectAtIndex:0]; 17 | } 18 | return nil; 19 | } 20 | 21 | - (BOOL)checkLogin { 22 | TFHppleElement *userLinkElement = [self searchFirstElementWithXPathQuery:@"//div[@id='Top']//table//td[@width='auto']//a[@class='top']"]; 23 | if (userLinkElement) { 24 | NSString *username =[[userLinkElement objectForKey:@"href"] stringByReplacingOccurrencesOfString:@"/member/" withString:@""]; 25 | if ([username length] > 0) { 26 | return YES; 27 | } 28 | } 29 | return NO; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /V2EX/Utilities/TFHppleElement+V2EXMethod.h: -------------------------------------------------------------------------------- 1 | // 2 | // TFHppleElement+V2EXMethod.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/18/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "TFHppleElement.h" 10 | 11 | @interface TFHppleElement (V2EXMethod) 12 | 13 | - (TFHppleElement *)searchFirstElementWithXPathQuery:(NSString *)query; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /V2EX/Utilities/TFHppleElement+V2EXMethod.m: -------------------------------------------------------------------------------- 1 | // 2 | // TFHppleElement+V2EXMethod.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/18/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "TFHppleElement+V2EXMethod.h" 10 | 11 | @implementation TFHppleElement (V2EXMethod) 12 | 13 | // TODO: Is there any better implementation 14 | - (TFHppleElement *)searchFirstElementWithXPathQuery:(NSString *)query { 15 | NSArray *retArray = [self searchWithXPathQuery:query]; 16 | if ([retArray count] > 0) { 17 | return [retArray objectAtIndex:0]; 18 | } 19 | return nil; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /V2EX/Utilities/UI/UIView+FrameMethods.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+FrameMethods.h 3 | // V2EX 4 | // 5 | // The file created and edited by WildCat on 2/11/14. 6 | // Reference: http://segmentfault.com/q/1010000000119371 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (FrameMethods) 12 | //Move methods 13 | 14 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical; 15 | 16 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded; 17 | 18 | - (void)moveToHorizontal:(CGFloat)horizontal; 19 | 20 | - (void)moveToVertical:(CGFloat)vertical; 21 | 22 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical; 23 | 24 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical setWidth:(CGFloat)width setHeight:(CGFloat)height; 25 | 26 | //Set width/height 27 | 28 | - (void)setWidth:(CGFloat)width height:(CGFloat)height; 29 | 30 | - (void)setWidth:(CGFloat)width; 31 | 32 | - (void)setHeight:(CGFloat)height; 33 | 34 | //Add width/height 35 | 36 | - (void)addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded; 37 | 38 | - (void)addWidth:(CGFloat)widthAdded; 39 | 40 | - (void)addHeight:(CGFloat)heightAdded; 41 | 42 | //Set corner radius 43 | 44 | - (void)setCornerRadius:(CGFloat)radius; 45 | 46 | - (void)setCornerRadius:(CGFloat)radius borderColor:(UIColor *)borderColor; 47 | 48 | - (CGRect)frameInWindow; 49 | @end -------------------------------------------------------------------------------- /V2EX/Utilities/UI/UIView+FrameMethods.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+FrameMethods.m 3 | // V2EX 4 | // 5 | // The file created and edited by WildCat on 2/11/14. 6 | // Reference: http://segmentfault.com/q/1010000000119371 7 | // 8 | 9 | #import "UIView+FrameMethods.h" 10 | 11 | @implementation UIView (FrameMethods) 12 | 13 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical 14 | { 15 | CGRect originRect = self.frame; 16 | CGRect newRect = CGRectMake(originRect.origin.x + horizontal, originRect.origin.y + vertical, originRect.size.width, originRect.size.height); 17 | self.frame = newRect; 18 | } 19 | 20 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded 21 | { 22 | CGRect originRect = self.frame; 23 | CGRect newRect = CGRectMake(originRect.origin.x + horizontal, 24 | originRect.origin.y + vertical, 25 | originRect.size.width + widthAdded, 26 | originRect.size.height + heightAdded); 27 | self.frame = newRect; 28 | } 29 | 30 | - (void)moveToHorizontal:(CGFloat)horizontal 31 | { 32 | CGRect originRect = self.frame; 33 | CGRect newRect = CGRectMake(horizontal, originRect.origin.y, originRect.size.width, originRect.size.height); 34 | self.frame = newRect; 35 | } 36 | 37 | - (void)moveToVertical:(CGFloat)vertical 38 | { 39 | CGRect originRect = self.frame; 40 | CGRect newRect = CGRectMake(originRect.origin.x, vertical, originRect.size.width, originRect.size.height); 41 | [self setFrame:newRect]; 42 | } 43 | 44 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical 45 | { 46 | CGRect originRect = self.frame; 47 | CGRect newRect = CGRectMake(horizontal, vertical, originRect.size.width, originRect.size.height); 48 | self.frame = newRect; 49 | } 50 | 51 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical setWidth:(CGFloat)width setHeight:(CGFloat)height 52 | { 53 | CGRect newRect = CGRectMake(horizontal, vertical, width, height); 54 | self.frame = newRect; 55 | } 56 | 57 | - (void)setWidth:(CGFloat)width height:(CGFloat)height 58 | { 59 | CGRect originRect = self.frame; 60 | CGRect newRect = CGRectMake(originRect.origin.x, originRect.origin.y, width, height); 61 | self.frame = newRect; 62 | } 63 | 64 | - (void)setWidth:(CGFloat)width 65 | { 66 | CGRect originRect = self.frame; 67 | CGRect newRect = CGRectMake(originRect.origin.x, originRect.origin.y, width, originRect.size.height); 68 | self.frame = newRect; 69 | } 70 | 71 | - (void)setHeight:(CGFloat)height 72 | { 73 | NSLog(@"set height"); 74 | CGRect originRect = self.frame; 75 | CGRect newRect = CGRectMake(originRect.origin.x, originRect.origin.y, originRect.size.width, height); 76 | self.frame = newRect; 77 | } 78 | 79 | - (void)addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded 80 | { 81 | CGRect originRect = self.frame; 82 | CGFloat newWidth = originRect.size.width + widthAdded; 83 | CGFloat newHeight = originRect.size.height + heightAdded; 84 | CGRect newRect = CGRectMake(originRect.origin.x, originRect.origin.y, newWidth, newHeight); 85 | self.frame = newRect; 86 | } 87 | 88 | - (void)addWidth:(CGFloat)widthAdded 89 | { 90 | [self addWidth:widthAdded addHeight:0]; 91 | } 92 | 93 | - (void)addHeight:(CGFloat)heightAdded 94 | { 95 | [self addWidth:0 addHeight:heightAdded]; 96 | } 97 | 98 | - (void)setCornerRadius:(CGFloat)radius 99 | { 100 | [self setCornerRadius:radius borderColor:[UIColor grayColor]]; 101 | } 102 | 103 | - (void)setCornerRadius:(CGFloat)radius borderColor:(UIColor *)borderColor 104 | { 105 | [self.layer setBackgroundColor:[[UIColor whiteColor] CGColor]]; 106 | [self.layer setBorderColor:[borderColor CGColor]]; 107 | [self.layer setBorderWidth:1.0]; 108 | [self.layer setCornerRadius:radius]; 109 | [self.layer setMasksToBounds:YES]; 110 | self.clipsToBounds = YES; 111 | } 112 | 113 | - (CGRect)frameInWindow 114 | { 115 | CGRect frameInWindow = [self.superview convertRect:self.frame toView:self.window]; 116 | return frameInWindow; 117 | } 118 | @end -------------------------------------------------------------------------------- /V2EX/Utilities/UI/V2EXAppSize.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAppSize.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/9/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface V2EXAppSize : NSObject 12 | 13 | + (CGFloat)getHeightWithController:(UIViewController *)controller; 14 | + (CGFloat)getWidthWithController:(UIViewController *)controller; 15 | + (CGFloat)getStatusBarHeight; 16 | + (CGFloat)getNavBarHeight4Controller:(UIViewController *)controller; 17 | 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /V2EX/Utilities/UI/V2EXAppSize.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAppSize.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/9/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXAppSize.h" 10 | 11 | @implementation V2EXAppSize 12 | 13 | + (CGFloat)_getSystemHeight { 14 | // CGRect rect = [[UIScreen mainScreen] bounds]; 15 | CGRect rect = [[UIScreen mainScreen] applicationFrame]; 16 | CGSize size = rect.size; 17 | return size.width; 18 | } 19 | 20 | + (CGFloat)_getSystemWidth { 21 | // CGRect rect = [[UIScreen mainScreen] bounds]; 22 | CGRect rect = [[UIScreen mainScreen] applicationFrame]; 23 | CGSize size = rect.size; 24 | return size.height; 25 | } 26 | 27 | + (CGFloat)getHeightWithController:(UIViewController *)controller { 28 | if (UIDeviceOrientationIsPortrait(controller.interfaceOrientation)) { 29 | return [self _getSystemWidth]; 30 | } else { 31 | return [self _getSystemHeight]; 32 | } 33 | } 34 | 35 | + (CGFloat)getWidthWithController:(UIViewController *)controller { 36 | if (UIDeviceOrientationIsPortrait(controller.interfaceOrientation)) { 37 | return [self _getSystemHeight]; 38 | } else { 39 | return [self _getSystemWidth]; 40 | } 41 | } 42 | 43 | + (CGFloat)getStatusBarHeight { 44 | CGRect rect = [[UIApplication sharedApplication] statusBarFrame]; 45 | return rect.size.height; 46 | } 47 | 48 | + (CGFloat)getNavBarHeight4Controller:(UIViewController *)controller { 49 | return controller.navigationController.navigationBar.frame.size.height; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /V2EX/Utilities/UI/V2EXMBProgressHUDUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMBProgressHUDUtil.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface V2EXMBProgressHUDUtil : NSObject 13 | 14 | + (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title; 15 | + (void)dismissGlobalHUD; 16 | + (MBProgressHUD*)showMessage:(NSString*)msg; 17 | @end 18 | -------------------------------------------------------------------------------- /V2EX/Utilities/UI/V2EXMBProgressHUDUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXMBProgressHUDUtil.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXMBProgressHUDUtil.h" 10 | 11 | #define ALPHA 0.70f 12 | 13 | @implementation V2EXMBProgressHUDUtil 14 | 15 | + (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title { 16 | UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject]; 17 | [MBProgressHUD hideAllHUDsForView:window animated:YES]; 18 | 19 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES]; 20 | hud.labelText = title; 21 | hud.alpha = ALPHA; 22 | hud.userInteractionEnabled = NO; 23 | return hud; 24 | } 25 | 26 | + (void)dismissGlobalHUD { 27 | UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject]; 28 | [MBProgressHUD hideHUDForView:window animated:YES]; 29 | } 30 | 31 | + (MBProgressHUD*)showMessage:(NSString*)msg { 32 | UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject]; 33 | // [MBProgressHUD hideAllHUDsForView:window animated:YES]; 34 | 35 | static MBProgressHUD* hud = nil; 36 | // if (!hud) { 37 | hud = [MBProgressHUD showHUDAddedTo:window animated:YES]; 38 | // } 39 | hud.mode = MBProgressHUDModeText; 40 | hud.labelText = msg; 41 | hud.hidden = NO; 42 | hud.alpha = ALPHA; 43 | [hud hide:YES afterDelay:2.5f]; 44 | hud.userInteractionEnabled = NO; 45 | return hud; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /V2EX/Utilities/V2EXGlobalCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXGlobalCache.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface V2EXGlobalCache : NSCache 12 | 13 | + (V2EXGlobalCache *)cache; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /V2EX/Utilities/V2EXGlobalCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXGlobalCache.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXGlobalCache.h" 10 | #import "V2EXAppDelegate.h" 11 | 12 | @implementation V2EXGlobalCache 13 | 14 | + (V2EXGlobalCache *)cache { 15 | static V2EXGlobalCache *_cache = nil; 16 | static dispatch_once_t predicate; 17 | dispatch_once(&predicate, ^{ 18 | _cache = [[V2EXGlobalCache alloc] init]; 19 | }); 20 | 21 | return _cache; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /V2EX/Utilities/V2EXStringUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXStringUtil.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/14/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface V2EXStringUtil : NSObject 12 | 13 | + (NSUInteger)link2TopicID:(NSString *)urlString; 14 | + (NSString *)replaceUnicode:(NSString *)unicodeStr; 15 | + (NSString *)stringByStrippingHTML:(NSString *)inputString; 16 | + (NSString *)filterHTML:(NSString *)html; 17 | + (NSString *)hanldeAvatarURL:(NSString *)url; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /V2EX/Utilities/V2EXStringUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXStringUtil.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/14/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXStringUtil.h" 10 | 11 | @implementation V2EXStringUtil 12 | 13 | /** 14 | * Conver link to topic ID 15 | * 16 | * @param urlString The link in V2EX topics list. 17 | * 18 | * @return topic ID. 19 | */ 20 | + (NSUInteger)link2TopicID:(NSString *)urlString{ 21 | NSError *error; 22 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"/t/[0-9]+#reply" 23 | options:0 24 | error:&error]; 25 | if (regex != nil) { 26 | NSArray *array = [regex matchesInString: urlString 27 | options: 0 28 | range: NSMakeRange( 0, [urlString length])]; 29 | if ([array count] > 0) { 30 | NSTextCheckingResult *match = [array objectAtIndex:0]; 31 | NSRange firstHalfRange = [match rangeAtIndex:0]; 32 | NSString *result = [[[urlString substringWithRange:firstHalfRange] stringByReplacingOccurrencesOfString:@"/t/" withString:@""] stringByReplacingOccurrencesOfString:@"#reply" withString:@""]; 33 | return (NSUInteger)[result integerValue]; 34 | } else { 35 | return 0; 36 | } 37 | } else { 38 | return 0; 39 | } 40 | } 41 | 42 | + (NSString *)filterHTML:(NSString *)html 43 | { 44 | NSScanner * scanner = [NSScanner scannerWithString:html]; 45 | NSString * text = nil; 46 | while([scanner isAtEnd] == NO) 47 | { 48 | [scanner scanUpToString:@"<" intoString:nil]; 49 | [scanner scanUpToString:@">" intoString:&text]; 50 | html = [html stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%@>",text] withString:@""]; 51 | } 52 | // NSString * regEx = @"<([^>]*)>"; 53 | // html = [html stringByReplacingOccurrencesOfString:regEx withString:@""]; 54 | return html; 55 | } 56 | 57 | + (NSString *)stringByStrippingHTML:(NSString *)inputString 58 | { 59 | NSMutableString *outString; 60 | 61 | if (inputString) 62 | { 63 | outString = [[NSMutableString alloc] initWithString:inputString]; 64 | 65 | if ([inputString length] > 0) 66 | { 67 | NSRange r; 68 | 69 | while ((r = [outString rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound) 70 | { 71 | [outString deleteCharactersInRange:r]; 72 | } 73 | } 74 | } 75 | 76 | return outString; 77 | } 78 | 79 | + (NSString *)replaceUnicode:(NSString *)unicodeStr { 80 | 81 | NSString *tempStr1 = [unicodeStr stringByReplacingOccurrencesOfString:@"\\u" withString:@"\\U"]; 82 | NSString *tempStr2 = [tempStr1 stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 83 | NSString *tempStr3 = [[@"\"" stringByAppendingString:tempStr2] stringByAppendingString:@"\""]; 84 | NSData *tempData = [tempStr3 dataUsingEncoding:NSUTF8StringEncoding]; 85 | NSString* returnStr = [NSPropertyListSerialization propertyListFromData:tempData 86 | mutabilityOption:NSPropertyListImmutable 87 | format:NULL 88 | errorDescription:NULL]; 89 | 90 | // NSLog(@"Output = %@", returnStr); 91 | 92 | return [returnStr stringByReplacingOccurrencesOfString:@"\\r\\n" withString:@"\n"]; 93 | } 94 | 95 | /** 96 | * Handle V2EX avatar URL 97 | * 98 | * @param url avatar URL 99 | * 100 | * @return avatar url with protocol name http or https(not implementation yet) 101 | */ 102 | + (NSString *)hanldeAvatarURL:(NSString *)url { 103 | return [@"http:" stringByAppendingString:url]; 104 | //TODO: Support https. 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /V2EX/V2EX-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | name.wildcat.v2ex 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | ${PRODUCT_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 0.1.2 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 2 29 | LSRequiresIPhoneOS 30 | 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UIStatusBarHidden 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /V2EX/V2EX-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /V2EX/V2EXAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAppDelegate.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "V2EXLatestTopicsViewController.h" 12 | #import "V2EXNodesListViewController.h" 13 | #import "V2EXSelfViewController.h" 14 | #import 15 | 16 | @interface V2EXAppDelegate : UIResponder 17 | 18 | @property (strong, nonatomic) UIWindow *window; 19 | 20 | @property (retain, nonatomic) V2EXNodesListViewController *sharedNodesListViewController; 21 | @property (retain, nonatomic) V2EXLatestTopicsViewController *sharedLatestTopicsViewController; 22 | @property (retain, nonatomic) V2EXSelfViewController *sharedSelfViewController; 23 | 24 | @property (strong, nonatomic) V2EXGlobalCache *cache; 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /V2EX/V2EXAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXAppDelegate.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXAppDelegate.h" 10 | #import 11 | #import 12 | #import "V2EXStringUtil.h" 13 | 14 | @implementation V2EXAppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | self.cache = [V2EXGlobalCache cache]; 19 | [self prepareForLaunching]; 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application 24 | { 25 | // 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. 26 | // 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. 27 | } 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application 30 | { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application 36 | { 37 | // 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. 38 | } 39 | 40 | - (void)applicationDidBecomeActive:(UIApplication *)application 41 | { 42 | // 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. 43 | } 44 | 45 | - (void)applicationWillTerminate:(UIApplication *)application 46 | { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | - (void)prepareForLaunching { 51 | [self prepareForNodeList]; 52 | [self prepareForAllControllers]; 53 | } 54 | 55 | - (void)prepareForNodeList { 56 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 57 | NSFileManager *fileMgr = [NSFileManager new]; 58 | 59 | NSString *documentDirectory = [paths objectAtIndex:0]; 60 | 61 | [fileMgr createDirectoryAtPath:[documentDirectory stringByAppendingString:@"/db"] withIntermediateDirectories:YES attributes:nil error:nil]; 62 | 63 | NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@"db/v2ex_normal.db"]; 64 | FMDatabase *db = [FMDatabase databaseWithPath:dbPath] ; 65 | if (![db open]) { 66 | NSLog(@"Could not open db."); 67 | } 68 | 69 | int nodesCount = [db intForQuery:@"SELECT COUNT(*) FROM nodes"]; 70 | 71 | if (nodesCount > 700) { 72 | NSLog(@"nodes > 700"); 73 | } else { 74 | [db executeUpdate:@"CREATE TABLE IF NOT EXISTS nodes (id integer NOT NULL PRIMARY KEY UNIQUE,name text NOT NULL,title text NOT NULL,uri text NOT NULL,topics integer NOT NULL,header text,footer text,created text NOT NULL)"]; 75 | 76 | 77 | NSBundle *bundle = [NSBundle mainBundle]; 78 | NSString *plistPath = [bundle pathForResource:@"NodesAll" ofType:@"json"]; 79 | NSData *jsonData = [NSData dataWithContentsOfFile:plistPath]; 80 | id json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil]; 81 | 82 | for (NSDictionary *row in json) { 83 | NSString *header, *footer; 84 | if ([row objectForKey:@"header"] != [NSNull null]) { 85 | header = [V2EXStringUtil stringByStrippingHTML:[row objectForKey:@"header"]]; 86 | } else { 87 | header = nil; 88 | } 89 | if ([row objectForKey:@"footer"] != [NSNull null]) { 90 | footer = [V2EXStringUtil stringByStrippingHTML:[row objectForKey:@"footer"]]; 91 | } else { 92 | footer = nil; 93 | } 94 | 95 | [db executeUpdate:@"INSERT OR REPLACE INTO nodes (id, name, title, uri, topics, header, footer, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 96 | [row objectForKey:@"id"], 97 | [row objectForKey:@"name"], 98 | [row objectForKey:@"title"], 99 | [[row objectForKey:@"url"] stringByReplacingOccurrencesOfString:@"http://www.v2ex.com/go/" withString:@""], 100 | [row objectForKey:@"topics"], 101 | header, 102 | footer, 103 | [row objectForKey:@"created"] 104 | ]; 105 | } 106 | } 107 | // V2EXJSONModel *nodesListModel = [[V2EXJSONModel alloc] initWithDelegate:self]; 108 | // [nodesListModel getAllNodes]; 109 | } 110 | 111 | - (void)prepareForAllControllers { 112 | _sharedLatestTopicsViewController = [V2EXLatestTopicsViewController sharedController]; 113 | _sharedNodesListViewController = [V2EXNodesListViewController sharedController]; 114 | _sharedSelfViewController = [V2EXSelfViewController sharedController]; 115 | } 116 | 117 | 118 | 119 | //- (void)requestDataSuccess:(id)dataObject { 120 | // _dictAllNodes = dataObject; 121 | //} 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /V2EX/Views/Categories/UIView+FrameMethods.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+FrameMethods.h 3 | // V2EX 4 | // 5 | // The file created and edited by WildCat on 2/11/14. 6 | // Reference: http://segmentfault.com/q/1010000000119371 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (FrameMethods) 12 | //Move methods 13 | 14 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical; 15 | 16 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded; 17 | 18 | - (void)moveToHorizontal:(CGFloat)horizontal; 19 | 20 | - (void)moveToVertical:(CGFloat)vertical; 21 | 22 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical; 23 | 24 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical setWidth:(CGFloat)width setHeight:(CGFloat)height; 25 | 26 | //Set width/height 27 | 28 | - (void)setWidth:(CGFloat)width height:(CGFloat)height; 29 | 30 | - (void)setWidth:(CGFloat)width; 31 | 32 | - (void)setHeight:(CGFloat)height; 33 | 34 | //Add width/height 35 | 36 | - (void)addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded; 37 | 38 | - (void)addWidth:(CGFloat)widthAdded; 39 | 40 | - (void)addHeight:(CGFloat)heightAdded; 41 | 42 | //Set corner radius 43 | 44 | - (void)setCornerRadius:(CGFloat)radius; 45 | 46 | - (void)setCornerRadius:(CGFloat)radius borderColor:(UIColor *)borderColor; 47 | 48 | - (CGRect)frameInWindow; 49 | @end 50 | -------------------------------------------------------------------------------- /V2EX/Views/Categories/UIView+FrameMethods.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+FrameMethods.m 3 | // V2EX 4 | // 5 | // The file created and edited by WildCat on 2/11/14. 6 | // Reference: http://segmentfault.com/q/1010000000119371 7 | // 8 | 9 | #import "UIView+FrameMethods.h" 10 | 11 | @implementation UIView (FrameMethods) 12 | 13 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical 14 | { 15 | CGRect origionRect = self.frame; 16 | CGRect newRect = CGRectMake(origionRect.origin.x + horizontal, origionRect.origin.y + vertical, origionRect.size.width, origionRect.size.height); 17 | self.frame = newRect; 18 | } 19 | 20 | - (void)moveHorizontal:(CGFloat)horizontal vertical:(CGFloat)vertical addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded 21 | { 22 | CGRect origionRect = self.frame; 23 | CGRect newRect = CGRectMake(origionRect.origin.x + horizontal, 24 | origionRect.origin.y + vertical, 25 | origionRect.size.width + widthAdded, 26 | origionRect.size.height + heightAdded); 27 | self.frame = newRect; 28 | } 29 | 30 | - (void)moveToHorizontal:(CGFloat)horizontal 31 | { 32 | CGRect origionRect = self.frame; 33 | CGRect newRect = CGRectMake(horizontal, origionRect.origin.y, origionRect.size.width, origionRect.size.height); 34 | self.frame = newRect; 35 | } 36 | 37 | - (void)moveToVertical:(CGFloat)vertical 38 | { 39 | CGRect origionRect = self.frame; 40 | CGRect newRect = CGRectMake(origionRect.origin.x, vertical, origionRect.size.width, origionRect.size.height); 41 | [self setFrame:newRect]; 42 | } 43 | 44 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical 45 | { 46 | CGRect origionRect = self.frame; 47 | CGRect newRect = CGRectMake(horizontal, vertical, origionRect.size.width, origionRect.size.height); 48 | self.frame = newRect; 49 | } 50 | 51 | - (void)moveToHorizontal:(CGFloat)horizontal toVertical:(CGFloat)vertical setWidth:(CGFloat)width setHeight:(CGFloat)height 52 | { 53 | CGRect newRect = CGRectMake(horizontal, vertical, width, height); 54 | self.frame = newRect; 55 | } 56 | 57 | - (void)setWidth:(CGFloat)width height:(CGFloat)height 58 | { 59 | CGRect origionRect = self.frame; 60 | CGRect newRect = CGRectMake(origionRect.origin.x, origionRect.origin.y, width, height); 61 | self.frame = newRect; 62 | } 63 | 64 | - (void)setWidth:(CGFloat)width 65 | { 66 | CGRect origionRect = self.frame; 67 | CGRect newRect = CGRectMake(origionRect.origin.x, origionRect.origin.y, width, origionRect.size.height); 68 | self.frame = newRect; 69 | } 70 | 71 | - (void)setHeight:(CGFloat)height 72 | { 73 | CGRect origionRect = self.frame; 74 | CGRect newRect = CGRectMake(origionRect.origin.x, origionRect.origin.y, origionRect.size.width, height); 75 | self.frame = newRect; 76 | } 77 | 78 | - (void)addWidth:(CGFloat)widthAdded addHeight:(CGFloat)heightAdded 79 | { 80 | CGRect originRect = self.frame; 81 | CGFloat newWidth = originRect.size.width + widthAdded; 82 | CGFloat newHeight = originRect.size.height + heightAdded; 83 | CGRect newRect = CGRectMake(originRect.origin.x, originRect.origin.y, newWidth, newHeight); 84 | self.frame = newRect; 85 | } 86 | 87 | - (void)addWidth:(CGFloat)widthAdded 88 | { 89 | [self addWidth:widthAdded addHeight:0]; 90 | } 91 | 92 | - (void)addHeight:(CGFloat)heightAdded 93 | { 94 | [self addWidth:0 addHeight:heightAdded]; 95 | } 96 | 97 | - (void)setCornerRadius:(CGFloat)radius 98 | { 99 | [self setCornerRadius:radius borderColor:[UIColor grayColor]]; 100 | } 101 | 102 | - (void)setCornerRadius:(CGFloat)radius borderColor:(UIColor *)borderColor 103 | { 104 | [self.layer setBackgroundColor:[[UIColor whiteColor] CGColor]]; 105 | [self.layer setBorderColor:[borderColor CGColor]]; 106 | [self.layer setBorderWidth:1.0]; 107 | [self.layer setCornerRadius:radius]; 108 | [self.layer setMasksToBounds:YES]; 109 | self.clipsToBounds = YES; 110 | } 111 | 112 | - (CGRect)frameInWindow 113 | { 114 | CGRect frameInWindow = [self.superview convertRect:self.frame toView:self.window]; 115 | return frameInWindow; 116 | } 117 | @end 118 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXNodesListCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNodesListCell.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/16/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "V2EXRequestDataDelegate.h" 11 | 12 | @interface V2EXNodesListCell : UITableViewCell 13 | 14 | @property (strong, nonatomic) IBOutlet UILabel *nodeTitle; 15 | @property (strong, nonatomic) IBOutlet UILabel *nodeHeader; 16 | 17 | @property (strong, nonatomic) NSString *nodeURI; 18 | @property(nonatomic, assign) id delegate; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXNodesListCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXNodesListCell.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/16/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXNodesListCell.h" 10 | 11 | @implementation V2EXNodesListCell 12 | 13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 14 | { 15 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 16 | if (self) { 17 | // Initialization code 18 | } 19 | return self; 20 | } 21 | 22 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 23 | { 24 | [super setSelected:selected animated:animated]; 25 | // Configure the view for the selected state 26 | } 27 | 28 | - (void)awakeFromNib { 29 | self.userInteractionEnabled = YES; 30 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellClick)]; 31 | [self addGestureRecognizer:tap]; 32 | // Add responder for click event. 33 | } 34 | 35 | - (void)cellClick { 36 | // TODO: This is very bad implementation to respond click event in UITableViewCell as a result of using UITableView in UIScrollView. Need to improve. The page view(DRPaginatedScrollView) in V2EXNodesListController should be replaced by one doesn't inherit form UIScrollView. 37 | [self.delegate requestTopicsList:self.nodeTitle.text andURI:self.nodeURI]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXNodesListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXTopicsListCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTopicsListCell.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface V2EXTopicsListCell : UITableViewCell 12 | @property (weak, nonatomic) IBOutlet UILabel *title; 13 | @property (weak, nonatomic) IBOutlet UIImageView *userAvatar; 14 | @property (weak, nonatomic) IBOutlet UILabel *nodeTitle; 15 | @property (weak, nonatomic) IBOutlet UILabel *replies; 16 | @property (weak, nonatomic) IBOutlet UILabel *username; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXTopicsListCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXTopicsListCell.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 2/2/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXTopicsListCell.h" 10 | 11 | @implementation V2EXTopicsListCell 12 | 13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 14 | { 15 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 16 | if (self) { 17 | // Initialization code 18 | [self setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth]; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 24 | { 25 | [super setSelected:selected animated:animated]; 26 | 27 | // Configure the view for the selected state 28 | } 29 | 30 | - (id)initWithCoder:(NSCoder*)aDecoder 31 | { 32 | self = [super initWithCoder:aDecoder]; 33 | 34 | if(self) 35 | { 36 | //Changes here after init'ing self 37 | } 38 | 39 | return self; 40 | } 41 | 42 | - (void)awakeFromNib 43 | { 44 | [self.replies.layer setCornerRadius:8]; 45 | [self.userAvatar.layer setCornerRadius:self.userAvatar.frame.size.width/5]; 46 | self.userAvatar.layer.masksToBounds = YES; 47 | 48 | self.layer.shouldRasterize = YES; 49 | self.layer.rasterizationScale = [[UIScreen mainScreen] scale]; 50 | } 51 | 52 | 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXTopicsListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 35 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXUserTopicsListCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXUserTopicsListCell.h 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/23/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface V2EXUserTopicsListCell : UITableViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *title; 14 | @property (weak, nonatomic) IBOutlet UILabel *nodeTitle; 15 | @property (weak, nonatomic) IBOutlet UILabel *replies; 16 | @property (weak, nonatomic) IBOutlet UILabel *lastReplyUsername; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXUserTopicsListCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // V2EXUserTopicsListCell.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 3/23/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import "V2EXUserTopicsListCell.h" 10 | 11 | @implementation V2EXUserTopicsListCell 12 | 13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 14 | { 15 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 16 | if (self) { 17 | // Initialization code 18 | } 19 | return self; 20 | } 21 | 22 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 23 | { 24 | [super setSelected:selected animated:animated]; 25 | 26 | // Configure the view for the selected state 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /V2EX/Views/TableViewCells/V2EXUserTopicsListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 31 | 38 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /V2EX/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /V2EX/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // V2EX 4 | // 5 | // Created by WildCat on 1/30/14. 6 | // Copyright (c) 2014 WildCat. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "V2EXAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([V2EXAppDelegate class])); 17 | } 18 | } 19 | --------------------------------------------------------------------------------