README
Just like software, this document will rot unless we take care of it. We encourage everyone to help us on that – just open an issue or send a pull request!
Why?
Getting on board with iOS can be intimidating. Neither Swift nor Objective-C are widely used elsewhere, the platform has its own names for almost everything, and it's a bumpy road for your code to actually make it onto a physical device. This living document is here to help you, whether you're taking your first steps in Cocoaland or you're curious about doing things "the right way". Everything below is just suggestions, so if you have a good reason to do something differently, by all means go for it!
Project Setup
A common question when beginning an iOS project is whether to write all views in code or use Interface Builder with Storyboards or XIB files. Both are known to occasionally result in working software. However, there are a few considerations:
Xcode Setup
Please follow the url [macbook-for-developers] [macbook-for-developers]:http://martiancraft.com/blog/2015/08/macbook-for-developers/
Why code?
- Storyboards are more prone to version conflicts due to their complex XML structure. This makes merging much harder than with code.
- It's easier to structure and reuse views in code, thereby keeping your codebase DRY.
Why Storyboards?
- For the less technically inclined, Storyboards can be a great way to contribute to the project directly, e.g. by tweaking colors or layout constraints. However, this requires a working project setup and some time to learn the basics.
- Iteration is often faster since you can preview certain changes without building the project.
- In Xcode 6, custom fonts and UI elements are finally represented visually in Storyboards, giving you a much better idea of the final appearance while designing.
Project Structure
To keep all those hundreds of source files ending up in the same directory, it's a good idea to set up some folder structure depending on your architecture. For instance, you can use the following:
- ├─ Classes
- ├─── Application Delegate
- ├─── Category
- ├─── Constant
- ├─── Models
- ├─── Network Manager
- ├─── Shared Manager
- ├─── Utility Function
- ├─── Shared Manager
- ├─── View Controller
- ├─ Views
- ├─── StoryBoard.storyboard
- ├─── LaunchScreen.xib
- ├─ Resources
- ├─── Slices
- ├─── Fonts
- ├─── Sounds
First, create them as groups (little yellow "folders") within the group with your project's name in Xcode's Project Navigator. Then, for each of the groups, link them to an actual directory in your project path by opening their File Inspector on the right, hitting the little gray folder icon, and creating a new subfolder with the name of the group in your project directory.
CocoaPods
If you're planning on including external dependencies (e.g. third-party libraries) in your project, CocoaPods offers easy and fast integration. Install it like so:
sudo gem install cocoapods
To get started, move inside your iOS project folder and run
pod init
This creates a Podfile, which will hold all your dependencies in one place. After adding your dependencies to the Podfile, you run
pod install
to install the libraries and include them as part of a workspace which also holds your own project. It is generally recommended to commit the installed dependencies to your own repo, instead of relying on having each developer running pod install
after a fresh checkout.
Note that from now on, you'll need to open the .xcworkspace
file instead of .xcproject
, or your code will not compile. The command
pod update
will update all pods to the newest versions permitted by the Podfile. You can use a wealth of operators to specify your exact version requirements.
Constants
Keep app-wide constants in a Constants.h
file that is included in the prefix header.
Instead of preprocessor macro definitions (via #define
), use actual constants:
static CGFloat const kBrandingFontSizeSmall = 12.0f;
static NSString * const kAwesomenessDeliveredNotificationName = @"foo";
Actual constants are type-safe, have more explicit scope (they’re not available in all imported/included files until undefined), cannot be redefined or undefined in later parts of the code, and are available in the debugger.
“Event” Patterns
These are the idiomatic ways for components to notify others about things:
- Delegation: (one-to-one) Apple uses this a lot (some would say, too much). Use when you want to communicate stuff back e.g. from a modal view.
- Callback blocks: (one-to-one) Allow for a more loose coupling, while keeping related code sections close to each other. Also scales better than delegation when there are many senders.
- Notification Center: (one-to-many) Possibly the most common way for objects to emit “events” to multiple observers. Very loose coupling — notifications can even be observed globally without reference to the dispatching object.
- Key-Value Observing (KVO): (one-to-many) Does not require the observed object to explicitly “emit events” as long as it is Key-Value Coding (KVC) compliant for the observed keys (properties). Usually not recommended due to its implicit nature and the cumbersome standard library API.
Assets
Asset catalogs are the best way to manage all your project's visual assets. They can hold both universal and device-specific (iPhone 4-inch, iPhone Retina, iPad, etc.) assets and will automatically serve the correct ones for a given name. Teaching your designer(s) how to add and commit things there (Xcode has its own built-in Git client) can save a lot of time that would otherwise be spent copying stuff from emails or other channels to the codebase. It also allows them to instantly try out their changes and iterate if needed.
Using Bitmap Images
Asset catalogs expose only the names of image sets, abstracting away the actual file names within the set. This nicely prevents asset name conflicts, as files such as [email protected]
are now namespaced inside their image sets. However, some discipline when naming assets can make life easier:
The modifiers -568h
, @2x
, ~iphone
and ~ipad
are not required per se, but having them in the file name when dragging the file to an image set will automatically place them in the right "slot", thereby preventing assignment mistakes that can be hard to hunt down.
Using Vector Images
You can include the original vector graphics (PDFs) produced by designers into the asset catalogs, and have Xcode automatically generate the bitmaps from that. This reduces the complexity of your project (the number of files to manage.)
Coding Style
Naming
Apple pays great attention to keeping naming consistent, if sometimes a bit verbose, throughout their APIs. When developing for Cocoa, you make it much easier for new people to join the project if you follow Apple's naming conventions.
Here are some basic takeaways you can start using right away:
A method beginning with a verb indicates that it performs some side effects, but won't return anything: - (void)loadView;
- (void)startAnimating;
Any method starting with a noun, however, returns that object and should do so without side effects: - (UINavigationItem *)navigationItem;
+ (UILabel *)labelWithText:(NSString *)text;
It pays off to keep these two as separated as possible, i.e. not perform side effects when you transform data, and vice versa. That will keep your side effects contained to smaller sections of the code, which makes it more understandable and facilitates debugging.
Building
Build Configurations
Even simple apps can be built in different ways. The most basic separation that Xcode gives you is that between debug and release builds. For the latter, there is a lot more optimization going on at compile time, at the expense of debugging possibilities. Apple suggests that you use the debug build configuration for development, and create your App Store packages using the release build configuration. This is codified in the default scheme (the dropdown next to the Play and Stop buttons in Xcode), which commands that debug be used for Run and release for Archive.
However, this is a bit too simple for real-world applications. You might – no, should! – have different environments for testing, staging and other activities related to your service. Each might have its own base URL, log level, bundle identifier (so you can install them side-by-side), provisioning profile and so on. Therefore a simple debug/release distinction won't cut it. You can add more build configurations on the "Info" tab of your project settings in Xcode.
xcconfig
files for build settings
Typically build settings are specified in the Xcode GUI, but you can also use configuration settings files (“.xcconfig
files”) for them. The benefits of using these are:
- You can add comments to explain things
- You can
#include
other build settings files, which helps you avoid repeating yourself:- If you have some settings that apply to all build configurations, add a
Common.xcconfig
and#include
it in all the other files - If you e.g. want to have a “Debug” build configuration that enables compiler optimizations, you can just
#include "MyApp_Debug.xcconfig"
and override one of the settings
- If you have some settings that apply to all build configurations, add a
- Conflict resolution and merging becomes easier
Find more information about this topic in these presentation slides.
Targets
A target resides conceptually below the project level, i.e. a project can have several targets that may override its project settings. Roughly, each target corresponds to "an app" within the context of your codebase. For instance, you could have country-specific apps (built from the same codebase) for different countries' App Stores. Each of these will need development/staging/release builds, so it's better to handle those through build configurations, not targets. It's not uncommon at all for an app to only have a single target.
Schemes
Schemes tell Xcode what should happen when you hit the Run, Test, Profile, Analyze or Archive action. Basically, they map each of these actions to a target and a build configuration. You can also pass launch arguments, such as the language the app should run in (handy for testing your localizations!) or set some diagnostic flags for debugging.
A suggested naming convention for schemes is MyApp (
:
MyApp (English) [Development]
MyApp (German) [Development]
MyApp [Testing]
MyApp [Staging]
MyApp [App Store]
For most environments the language is not needed, as the app will probably be installed through other means than Xcode, e.g. TestFlight, and the launch argument thus be ignored anyway. In that case, the device language should be set manually to test localization.
Deployment
Deploying software on iOS devices isn't exactly straightforward. That being said, here are some central concepts that, once understood, will help you tremendously with it.
Signing
Whenever you want to run software on an actual device (as opposed to the simulator), you will need to sign your build with a certificate issued by Apple. Each certificate is linked to a private/public keypair, the private half of which resides in your Mac's Keychain. There are two types of certificates:
-
Development certificate: Every developer on a team has their own, and it is generated upon request. Xcode might do this for you, but it's better not to press the magic "Fix issue" button and understand what is actually going on. This certificate is needed to deploy development builds to devices. Naming Convention : CERT_DEV_XXX.p12
-
Distribution certificate: There can be several, but it's best to keep it to one per organization, and share its associated key through some internal channel. This certificate is needed to ship to the App Store, or your organization's internal "enterprise app store". Naming Convention : CERT_DIST_XXX.p12
Provisioning
Besides certificates, there are also provisioning profiles, which are basically the missing link between devices and certificates. Again, there are two types to distinguish between development and distribution purposes:
-
Development provisioning profile: It contains a list of all devices that are authorized to install and run the software. It is also linked to one or more development certificates, one for each developer that is allowed to use the profile. The profile can be tied to a specific app or use a wildcard App ID (*). The latter is discouraged, because Xcode is notoriously bad at picking the correct files for signing unless guided in the right direction. Also, certain capabilities like Push Notifications or App Groups require an explicit App ID. Naming Convention : PP_DEV_XXX.provisioningprofile
-
Distribution provisioning profile: There are three different ways of distribution, each for a different use case. Each distribution profile is linked to a distribution certificate, and will be invalid when the certificate expires.
- Ad-Hoc: Just like development profiles, it contains a whitelist of devices the app can be installed to. This type of profile can be used for beta testing on 100 devices per year. For a smoother experience and up to 1000 distinct users, you can use Apple's newly acquired TestFlight service. Supertop offers a good summary of its advantages and issues.
- App Store: This profile has no list of allowed devices, as anyone can install it through Apple's official distribution channel. This profile is required for all App Store releases.
- Enterprise: Just like App Store, there is no device whitelist, and the app can be installed by anyone with access to the enterprise's internal "app store", which can be just a website with links. This profile is available only on Enterprise accounts. Naming Convention : PP_DIST_XXX.provisioningprofile
To sync all certificates and profiles to your machine, go to Accounts in Xcode's Preferences, add your Apple ID if needed, and double-click your team name. There is a refresh button at the bottom, but sometimes you just need to restart Xcode to make everything show up.
Debugging Provisioning
Sometimes you need to debug a provisioning issue. For instance, Xcode may refuse to install the build to an attached device, because the latter is not on the (development or ad-hoc) profile's device list. In those cases, you can use Craig Hockenberry's excellent Provisioning plugin by browsing to ~/Library/MobileDevice/Provisioning Profiles
, selecting a .mobileprovision
file and hitting Space to launch Finder's Quick Look feature. It will show you a wealth of information such as devices, entitlements, certificates, and the App ID. Use iPhone Configuration Utility
Uploading
iTunes Connect is Apple's portal for managing your apps on the App Store. To upload a build, Xcode 6 requires an Apple ID that is part of the developer account used for signing. This can make things tricky when you are part of several developer accounts and want to upload their apps, as for mysterious reasons any given Apple ID can only be associated with a single iTunes Connect account. One workaround is to create a new Apple ID for each iTunes Connect account you need to be part of, and use Application Loader instead of Xcode to upload the builds. That effectively decouples the building and signing process from the upload of the resulting .app
file.
After uploading the build, be patient as it can take up to an hour for it to show up under the Builds section of your app version. When it appears, you can link it to the app version and submit your app for review.
Making iPA From Xcode project
Use following steps to make a signed ipa :
- Select Target: Select your SampleProject in your xcode(left-hand side as shown) Then select Product option, in which you could select Archive option. Click it.
- Select Archive: It will take you to Window > Organizer You can see your recent Archive files. Select your archive file and right click > Click Show in finder. You can now get the location of archive file
- Terminal cd: Open Terminal and change your directory to current xcodeArchive folder cd /Users/macpro/Library/Developer/Xcode/Archives/2015-07-22/
- Export Archive: Now run this command : xcodebuild -exportArchive -exportFormat ipa -archivePath "SampleProject.xcarchive" -exportPath "SampleProject_22July.ipa" -exportProvisioningProfile "<#Project Name#> Development Profile"
- Get Exported ipa: If Export is succeeded then you will have an IPA in /Users/macpro/Library/Developer/Xcode/Archives/2015-07-22/ folder. OR [Use Xcode ipa export plugin][Xcode-Plugin-Export-IPA] [Xcode-Plugin-Export-IPA]: https://github.com/rajeshbeats/Xcode-Plugin-Export-IPA
Xcode Plugins :
Following are the plugins that I suggest you to use :
- Alcatraz : Alcatraz is an open-source package manager for Xcode 5+. It lets you discover and install plugins, templates and color schemes without the need for manually cloning or copying files. It installs itself as a part of Xcode and it feels like home. [alcatraz] [alcatraz]: https://github.com/supermarin/Alcatraz
New Xcode Plugin support :
To Give plugin support to any new Xcode version you need to know its application name. Follow these steps and you can enable all plugins on the new Xcode version. Write this command in terminal and tell me. * find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID
Step by step SWIFT integration for Xcode Objc-based project:
Following the link StackOverFlow
- 1: Create new *.swift file (in Xcode) or add it by using Finder
- 2: Add swift bridging empty header if Xcode have not done this before (see 4 below)
- 3: Implement your Swift class by using @ objc attribute: #import UIKit @ objc class Hello : NSObject { func sayHello () { println("Hi there!") } }
- 4 :Open Build Settings and check this parameters:
- Product Module Name : myproject
- Defines Module : YES
- Embedded Content Contains Swift : YES
- Install Objective-C Compatibility Header : YES
- Objective-C Bridging Header : $(SRCROOT)/Sources/SwiftBridging.h
- 5: Import header (which is auto generated by Xcode) in your *.m file (#import "myproject-Swift.h")
- 6: Clean and rebuild your Xcode project
- 7: Profit!
Change Project name,Class Prefix and Organization Name :
- On the left side expand Targets
- Click on SampleProject in left pane and Edit it.
- Double click on your target and then select build tab in the newly opened window
- on the top right under Identity & Type - edit - Project Name
- on the top right under Project document edit - Organisation and Class Prefix
- Change it and clean rebuild, your new app name should be changed by now.
Integrate GoogleMapsHelper
GoogleMapsHelper
Read Me in Russian : http://gargo.of.by/googlemapshelper/
A GOOGLE MAPS Helper that help you do multiple tasks like
HOW TO USE
// using AFNetworking
[[AFGoogleMapsHelper sharedAFGoogleMapsHelper] geocodeAddressString:@"Arsenal Emirates" components:@{} completionHandler:^(MOGoogleGeocodeList *googleGeoCodeList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
CLLocationCoordinate2D emiratesStadium = { 51.555747, -0.108309};
CLLocationCoordinate2D stamfordBridge = { 51.481690, -0.190999 };
[[AFGoogleMapsHelper sharedAFGoogleMapsHelper] reverseGeocodeCoordinate:(emiratesStadium) resultTypes:@[] locationTypes:@[] completionHandler:^(MOGoogleGeocodeList *googleGeoCodeList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
[[AFGoogleMapsHelper sharedAFGoogleMapsHelper] getAutoCompleteFromGoogle:@"Arsenal Emirates Stadium, london" andAutoComplete:^(MOGoogleAutoCompleteList *googleAutocompleteList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
[[AFGoogleMapsHelper sharedAFGoogleMapsHelper] getDirections:emiratesStadium andCoordinateDestination:stamfordBridge andDrawPoints:^{
} andPlaceMarks:^(MKPolyline *polyLine, NSString *distance, NSString *duration, NSString *startAddress, NSString *endAddress, NSMutableArray *polyLineSetArray, NSMutableArray *directionsSetArray, NSMutableArray *distanceSetArray) {
}];
// Using SVHTTPClient
[[SVGoogleMapsHelper sharedGoogleMapHelper] geocodeAddressString:@"Arsenal Emirates" components:@{} completionHandler:^(MOGoogleGeocodeList *googleGeoCodeList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
[[SVGoogleMapsHelper sharedGoogleMapHelper] reverseGeocodeCoordinate:(emiratesStadium) resultTypes:@[] locationTypes:@[] completionHandler:^(MOGoogleGeocodeList *googleGeoCodeList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
[[SVGoogleMapsHelper sharedGoogleMapHelper] getAutoCompleteFromGoogle:@"Arsenal Emirates Stadium, london" andAutoComplete:^(MOGoogleAutoCompleteList *googleAutocompleteList, SPGoogleGeoCoderResponse responseCode, NSString *message) {
}];
[[SVGoogleMapsHelper sharedGoogleMapHelper] getDirections:emiratesStadium andCoordinateDestination:stamfordBridge andDrawPoints:^{
} andPlaceMarks:^(MKPolyline *polyLine, NSString *distance, NSString *duration, NSString *startAddress, NSString *endAddress, NSMutableArray *polyLineSetArray, NSMutableArray *directionsSetArray, NSMutableArray *distanceSetArray) {
}];
1- Geocode
It returns all these items :
- MOGoogleGeocodeList *googleGeoCodeList,
- SPGoogleGeoCoderResponse responseCode,
- NSString *message
I Geocode @"Arsenal Emirates" and I got Printing description of googleGeoCodeList->_results->[0]:
{ "formatted_address" = "Hornsey Rd, London N7 7AJ, UK"; geometry = { bounds = { }; location = { lat = "51.5548885"; lng = "-0.108438"; }; "location_type" = APPROXIMATE; viewport = { northeast = { lat = "51.55623748029149"; lng = "-0.107089019708498"; }; southwest = { lat = "51.5535395197085"; lng = "-0.109786980291502"; }; }; }; kMOGoogleGeocodePlacemarksAddressComponents = ( { kMOAddressComponentsTypes = ( route ); "long_name" = "Hornsey Road"; "short_name" = "Hornsey Rd"; }, { kMOAddressComponentsTypes = ( "postal_town" ); "long_name" = London; "short_name" = London; }, { kMOAddressComponentsTypes = ( "administrative_area_level_2", political ); "long_name" = "Greater London"; "short_name" = "Greater London"; }, { kMOAddressComponentsTypes = ( "administrative_area_level_1", political ); "long_name" = England; "short_name" = England; }, { kMOAddressComponentsTypes = ( country, political ); "long_name" = "United Kingdom"; "short_name" = GB; }, { kMOAddressComponentsTypes = ( "postal_code" ); "long_name" = "N7 7AJ"; "short_name" = "N7 7AJ"; } ); kMOGoogleGeocodePlacemarksTypes = ( establishment, "point_of_interest", stadium ); "place_id" = "ChIJO14pRXYbdkgRkM-CgzxxADY"; }
2- Reverse Geocode
It returns all these items :
- MOGoogleGeocodeList *googleGeoCodeList,
- SPGoogleGeoCoderResponse responseCode,
- NSString *message
Printing description for first item :
<__NSArrayI 0x6080000b1b20>( { "formatted_address" = "Emirates Stadium, London, UK"; geometry = { bounds = { northeast = { lat = "51.5561569"; lng = "-0.1069905"; }; southwest = { lat = "51.5539356"; lng = "-0.1098853"; }; }; location = { lat = "51.55572979999999"; lng = "-0.1083118"; }; "location_type" = ROOFTOP; viewport = { northeast = { lat = "51.5563952302915"; lng = "-0.1069905"; }; southwest = { lat = "51.5536972697085"; lng = "-0.1098853"; }; }; }; kMOGoogleGeocodePlacemarksAddressComponents = ( { kMOAddressComponentsTypes = ( premise ); "long_name" = "Emirates Stadium"; "short_name" = "Emirates Stadium"; }, { kMOAddressComponentsTypes = ( locality, political ); "long_name" = London; "short_name" = London; }, { kMOAddressComponentsTypes = ( "postal_town" ); "long_name" = London; "short_name" = London; }, { kMOAddressComponentsTypes = ( "administrative_area_level_2", political ); "long_name" = "Greater London"; "short_name" = "Greater London"; }, { kMOAddressComponentsTypes = ( "administrative_area_level_1", political ); "long_name" = England; "short_name" = England; }, { kMOAddressComponentsTypes = ( country, political ); "long_name" = "United Kingdom"; "short_name" = GB; } ); kMOGoogleGeocodePlacemarksTypes = ( premise ); "place_id" = ChIJuaX4rXcbdkgRX7nJ4iCVzT0; }} )
3- Autocomplete
It Returns all of these items :
- MOGoogleAutoCompleteList *googleAutocompleteList,
- SPGoogleGeoCoderResponse responseCode,
- NSString *message
I wanted to search @"Arsenal Emirates Stadium, london" and I got following 2 results, I am showing first item
Printing description of ((MOPredictions *)0x0000600000282b70): { description = "Arsenal Football Club, Emirates Stadium, Hornsey Road, London, United Kingdom"; id = 695fdbc199ef136a3674dc5c3946d0901be24cf2; kMOPredictionsMatchedSubstrings = ( { length = 7; offset = 0; }, { length = 16; offset = 23; }, { length = 6; offset = 55; } ); kMOPredictionsTerms = ( { offset = 0; value = "Arsenal Football Club"; }, { offset = 23; value = "Emirates Stadium"; }, { offset = 41; value = "Hornsey Road"; }, { offset = 55; value = London; }, { offset = 63; value = "United Kingdom"; } ); kMOPredictionsTypes = ( establishment ); "place_id" = ChIJq3Y4mXYbdkgRinA5RgGR5tA; reference = "CmRcAAAA3_03PcjmlvYYAMB56q1NSPHAa6o4s5OZlZzmqKWVzl6m8wQu8kIAHqSFzY8M_fJC6tbdt5vQSOylmlp6vu8hMJ0areyjFCiETtOb2e1qkM9a8TbnHRoIGK83-h0iy9EaEhCgUDC5ODRWWeKhZZmXh3wHGhRRAUwm4UFKR6a689AJXsADrqKFNA"; }
4- Directions
It Returns All these Items :
- MKPolyline *polyLine,
- NSString *distance,
- NSString *duration,
- NSString *startAddress,
- NSString *endAddress,
- NSMutableArray *polyLineSetArray,
- NSMutableArray *directionsSetArray,
- NSMutableArray *distanceSetArray in a block.
I found directions between following CLLocationCoordinate2D's CLLocationCoordinate2D emiratesStadium = { 51.555747, -0.108309}; CLLocationCoordinate2D stamfordBridge = { 51.481690, -0.190999 };
Printing description of duration:
42 mins
Printing description of distance:
16.6 km
Printing description of startAddress:
Citizen Rd, London N7, UK
Printing description of endAddress:
19 Billing Pl, London SW10 9UN, UK
Plus Polyline object to be used in MKMapView
It also tells you Guidance strings which you can use :
- Head southwest on Citizen Rd toward Hornsey Rd/A103,
- Turn right onto Hornsey Rd/A103,
- Turn left onto Tollington Rd/A503Continue to follow A503,
- Continue straight onto Camden Rd/A503,
- Turn left onto Camden St/A400Continue to follow A400,
- Turn left onto Hampstead Rd/A400Continue to follow A400,
- Turn right onto Euston Rd,
- Merge onto Euston Rd/A501 via the ramp to Ring Road/A41/A40/KilburnContinue to follow A501,
- Keep right to continue on Marylebone Flyover/A40Continue to follow A40,
- Take the A3220 ramp to Hammersmith/Shepherd's Bush/White City/Earls Court,
- At the roundabout, take the 1st exit onto W Cross Rte/A3220,
- At the roundabout, take the 2nd exit onto Holland Rd/A3220Continue to follow A3220,
- Keep right to continue on Warwick Gardens/A3220,
- Turn left onto Pembroke Rd/A3220Continue to follow A3220,
- Continue straight onto Earls Ct Rd/A3220Continue to follow A3220,
- Turn right onto Fulham Rd/A308Continue to follow Fulham Rd,
- Turn right,
- Turn right,
- Turn left
- Destination will be on the left
Make sure you integrate AFNetworking, SVProgressHUD, SVHTTPClient
I was using CocoaPods so I used
- pod 'SVHTTPRequest', '~> 0.5'
- pod 'AFNetworking', '~> 3.0'
- pod 'SVProgressHUD'
Dont forget to add condition in info.plist
- App Transport > Arbitrary loads allow : YES