Multiple platform support for Single Target in podfile - ios

Is there any way to add multiple platform support for a single target through podfile?
For example my project is common for both iOS and Mac. They consume the same code base. So instead of creating multiple target for the same code, I added support for both iOS and MacOSX in the same target. It builds fine.
Now I want to add a dependency through Cocoapods. I create a podfile and specify my target's dependency on the pod. The pod in question here supports multiple platform in a similar way i.e. single target.
But now while building my project it fails for iOS.
Specifying multiple platforms in Podfile for single target produces an error.
And if I just specify platform as only iOS or Mac then the project in question fails to build on other platform.
Has anyone experienced this before?
How can I add multiple platform for a single target through podfile?
P.S. - I know I can achieve it by creating multiple targets in my project. But I want to keep that as my last option.

def import_pods
pod 'CorePlot'
end
target 'FirstAppWithMacOS' do
# define your platform here.
platform :ios, '9.0'
import_pods
end
target 'FirstMacOSApp' do
# define your platform here.
platform :osx, '10.10'
import_pods
end
See below image of my project :

The platform device is controlled by Base SDK and Supported Platforms in Architectures section in your target's Build Settings. If you didn't create the separated two targets for both macOS and iOS devices, there should be only one choice left for you to support those platform entries, that is, duplicate new two existing build configurations for macOS platform, then you could configure the settings separately.
For example, you have an existing iOS-based project and there are two default build configurations named Debug and Release. Now try to two new one for macOS, choose the main Xcode project -> choose project name (not target name here) -> select Info section in top -> tap + button under the Configurations section -> choose Duplicate "Debug"/"Release" Configuration -> rename it as Debug-macOS/Release-macOS or others, rename the original Debug/Release to Debug-iOS/Release-iOS, too.
Now you have two pairs of configurations to debug and archive the targeted platforms under the only one target's build settings. You could also create a new scheme for macOS development, just make sure that you choose the right configurations in different actions.
Talking to the main Podfile on cocoapods, if you maintain a cocoapods library by yourself and you want to add it as a dependency, it'd better support both macOS and iOS platform in your podspec file, of course, you need to make compatibility for those platforms, AFNetworking did it like that. Here is the main conversion task, do not use any API under the UIKit for macOS platform. To distinguish the platform difference in compiling time, you could use these macros for that,
#if TARGET_OS_IOS
// iOS supported
#elif TARGET_OS_OSX
// macOS supported
#endif
If you want to use a third-party library in your standalone project target, check the compatible issue first, if it only works for iOS platform, you could specify the dependent library for only one platform like this,
pod 'PonyDebugger', :configurations => ['Debug-iOS', 'Release-iOS']
Same with macOS supported.
Hope it helps!

Short answer
Is is not supported as of CocoaPods 1.10.2. There are reasons why it isn't supported and probably won't be supported any time soon or only when building with Xcode 13.
Long answer
When integrating a pod to a target, CocoaPods (CP) must perform a couple of steps to make that happen. I won't list all of them here but the following four are relevant to this answer:
Create an umbrella header.
Build a (dynamic/static) framework or static library.
Adding the umbrella header to the header search path of the target.
Liking the built framework/library to the target.
Let me go through these steps one by one:
1. Create an umbrella header
The content of the umbrella header depends on the platform the pod is built for. E.g. if it is macOS, it will import Cocoa, if it is iOS, it will import UIKit. As umbrella headers are treated in a special way, especially when using modules (and when dealing with Swift code as well, you do want to use modules, trust me), using #ifdef to make parts of them platform dependent is a rather bad idea. So to support multiple platforms, CP would need to create one umbrella header per platform.
Please note that this is exactly what CP does, if you integrate the same pod into two targets for two different platforms. E.g. if one target is macOS and one is iOS, there will be two umbrella headers, one ending in -macOS.h and one ending in -iOS.h. So in theory, this functionality is there, there is just no way to specify more than one platform per target so far and thus the created umbrella header will only work with one target platform. That is what Vikas Dadheech probably meant when he said:
And if I just specify platform as only iOS or Mac
then the project in question fails to build on other platform.
Currently the created header will only play nice with either platform. Please note that this also holds true for any auto-generated module map file (.modulemap).
2. Build a (dynamic/static) framework or static library
Just as before, how the library/framework is built depends on the platform as a Podspec file can specify different build options per platform and even select different files to be included in a build depending on platform, the pod must be built separately per platform, even if both platforms would use the same CPU instruction set (e.g. ARM Mac and ARM iPad) and the code otherwise contains nothing that is platform specific.
And just as before, this functionality is there when integrating the same pod in into different targets for different platforms, resulting in two libraries/frameworks with a platform suffix.
3. Adding the umbrella header to the header search path of the target
Now its getting a bit more tricky. Assuming we have two umbrella headers (and maybe also two module map files), only one of them must be visible when building the target they integrate into and which one depends the target platform. This is no problem when we are talking about different targets as different build settings per target are easy but in that case it's a single target and this comes with some implications.
Yet even that is solvable, e.g. by making settings depend on SDK, after all you will require a different SDK per platform, so you can do things like this:
HEADER_SEARCH_PATHS[sdk=macosx*] = ....
HEADER_SEARCH_PATHS[sdk=iphoneos*] = ...
to ensure that only the headers and map files for the correct platform are visible for the build.
4. Liking the built framework/library to the target
And here comes the problem: If you want to use Xcode's linking, you can either link against the macOS or the iOS version you've built in step 2 but you cannot link to either one depending on the target platform you are building for. So how do you make sure the binary links against the correct library and only one of them?
You can ...
combine all versions into a universal binary and always link against this one. This will work as long as your target platforms have different CPU architectures (e.g. x86 for Mac and ARM for iOS) but it won't work if your platforms have equal ones (e.g. ARM Mac and ARM iPhone). With the instruction of ARM Macs, this option is pretty much dead and it was already not possible before when your two targets are iOS and tvOS since both have been ARM, too.
not use Xcode's linking at all and instead set linking flags by hand as well as library search paths using the same trick as for the headers above. Well, this will only work with static libraries and frameworks out of the box and only reliably with the new Xcode build system if you manually set target dependencies. With dynamic frameworks you also manually must copy the frameworks, sometimes (re-)sign them correctly, and also manage dependencies by hand. All possible, but seems rather fragile and may have even more implications than I just named above. I guess this is a somewhat risky approach with lots of possible pitfalls and some setups will simply not work that way at all, no matter what you do.
use the selective linking feature of Xcode 13. This is exactly what is needed here and Apple has probably implemented it as the CPU architecture no longer is suitable for many linking cases. Yet Xcode 13 is currently still in beta and if the feature relies upon that capability, it will require using Xcode 13 for building and this requires a Mac running at least Big Sur.

If You want to support multiple platforms in a single target in your Podfile, You can use the platform directive to specify the platforms you want to support. For Example
platform :ios, '9.0'
platform :watchos, '3.0'
Also you can specify a minimum version and a maximum version for each platform. For Example
platform :ios, '9.0'..'14.0'

I did same kind of implementation in my one of project which has 3 targets.
I have created 'def' shared pods and then call this shared_pods in all 3 targets. In this way, It works pretty well. I did only for iOS app.
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
def shared_pods
pod 'Stripe'
pod 'Google/SignIn'
end
target 'App_Dev' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end
target 'App_QA' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end
target 'App_Release' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end

Related

iOS build size with Swift 3 and pods

I just noticed cocoapods with Swift increases build size, however when I use same libraries manually then build size is normal.
I created a blank project with some pods and the build size goes to the 10MB and .app file is around 40MB.
I also notices that my .app file contains all my cocoapods frameworks
(around 37MB) by viewing "Package content".
My podfile having these pods
pod 'Alamofire', '~> 4.4'
pod 'SwiftyJSON'
pod 'IQKeyboardManagerSwift'
pod 'ActionSheetPicker-3.0'
pod 'Kingfisher'
pod 'JKNotificationPanel'
My questions are
why my .app file contains all framework, I guess it happens only with Swift and cocoapod (Correct me if I am wrong) ?
How can we reduce the size of build by using cocoapods with Swift
Thanks in advance
If you use the libraries as static libraries, the linker can exclude the parts of them that you don't use from the build. The same is not true for frameworks. So if you just made an app with those libraries and didn't use them, they won't be included at all, so it's not a fair comparison.
When you ask why your app contains all frameworks I assume you mean the ones for the swift runtime and not the dependencies you explicitly asked for in cocoapods. All swift apps have the runtime bundled into them, at least until the runtime becomes stable enough (changes very often nowadays), and then the phone OS will contain a few versions of it and we won't have to include it in the app.
Also, don't get terrified by the app size. The actual size of the app the user downloads is much smaller. You can see it in iTunesConnect's activity tab and then picking your build, once you've uploaded it there obviously. I've seen apps that upload as 120MB or so to iTunesConnect and then the final download to the user is 20 to 30MB.
Edit after getting more info:
You said you are comparing dragging the sources of libraries into your project vs cocoapods, and there's a clear difference here: if you add the source files of the library it's not the same as adding the compiled framework for the library. If you add the sources, a lot of the unused stuff will be optimized out, thus affecting the size. Example of this is when a library includes a category that the library itself is not using. Unless some linker flags are used, the category is optimized out and it doesn't work on the app using the library (you can search for all_load if you want more info). When using frameworks nothing is optimized out, but this is not happening due to Cocoapods. You can use frameworks without Cocoapods, and you should get exactly the same results regarding size.
Edit 2:
It looks like swift 5 might achieve ABI stability! We might be able to choose swift for apps that need to be as small as possible now with it!

Cocoapods Dependencies in Dynamic Framework Module

I have an app that has three targets:
App Target
Shared Dynamic Framework
Keyboard Extension
The Xcode project specifies Shared as a target dependency in App and Keyboard (the library has code that is required by both).
My Podfile has a "target" block for each target where I define dependencies. Now, my assumption used to be that any libraries included in "Shared" as Cocoapods dependencies would by virtue of doing so also be available to the App and Keyboard Xcode targets. This however does not seem to be the case at this point. If I instead try to import the a given dependency in all 3 targets, I get duplicate symbols.
Can someone explain to me the proper way to achieve what I'm looking for?

ObjC: Statics are not consistent across multiple frameworks when contained in cocoapod

So my situation specifically uses the Objection dependency injection library. However, I believe this could be an issue for other libraries as well.
Platform
iOS 8.0 Xcode 6.1.1. Cocoapods 0.35. Objective-C.
Set up:
I have 4 projects. 3 universal frameworks and a test app (I'm creating an api). The test app does not utilize cocoapods (though some other app might).
All three of my frameworks need to use injection. I created a pod file that looks like
workspace 'workspace.xcworkspace'
platform :ios, '8.0'
xcodeproj 'path/to/proj/one'
xcodeproj 'path/to/proj/two'
xcodeproj 'path/to/proj/three'
target :Project1 do
pod 'Objection', '1.4'
end
target :Project2 do
pod 'Objection', '1.4'
end
target :Project3 do
pod 'Objection', '1.4'
pod 'SDWebImage', '~>3.7.1'
end
Problem
When I use this set up singletons don't work in Objection. However, they only don't work if they (the singleton class) is defined in project 1 and then I call [JSObjection createInjector]; in project 2 (the projects don't make a difference, it is that create is called in a different project to the definition).
Current theory
After battling with this for a while I believe it is due to the warning along the lines of:
Objection is defined in Project 1, Project 2 and Project 3.
Which one will be used undefined.
So when the class registers itself with the injection system (e.g through objection_register_singleton(ClassX)). It is using the JSObjection definition from its project, which might not be the one being used for a class that injects it (e.g. objection_requires_sel(#selector(myClassXObject))).
Question
I want to be able to use the iOS frameworks setup as, from what I understand, it is better overall than static libs. However, I also want to be able to use Cocoapods (and have any app, that uses my api, use Cocoapods).
How do I either a) share one definition across multiple frameworks or b) setup a framework as a Cocoapod (I've seen that this is being developed).
I would love to be able to keep the frameworks linked through the xcode workspace so when one is built the frameworks it depends on are also built. Though having said that, my api will probably become a Cocoapod at some point anyway.
Thanks in advance (and for reading to here)
Indigo

How to handle Debug/TestFlight/AppStore releases without targets?

I use Cocoapods for my project and I need a specific set of frameworks for each Debug/TestFlight/AppStore release:
Debug (simulator or device): common frameworks + Calabash - Google Analytics
TestFlight: common frameworks + TestFlight SDK
App Store: common frameworks only
I could use targets, but they are annoying:
Every time you add a new file (or create one) you have to remember to add it to all targets. It makes it really easy to produce builds that don't have a required file.
Changes in build settings of one target don't propagate to other targets (i.e: changing a llvm flag).
On the other hand, cocoapods doesn't support weak frameworks, so I can't weak-link to Calabash in my main target and force-load it when building in Debug mode (because it'll still be in the framework set when building for the app store)
What would be a good balance between the very segregated option that are targets, and "all in" solution of a single target?
I have a makefile that removes certain pods then runs Pod install. We're making it much easier in the next release to do config based pods, so you will be able to do all of what your asking in version 0.34+. This is in the current HEAD on github.

Building a static library with cocoapods

I am trying to build a static library that has different dependencies (AFNetworking for example) specified in a Podfile. I don't want the dependencies to be included in the final static library (call libMyProject.a), I just want to link against them and then create a MyProject.Podspec file where I can put the same dependencies.
The problem is that when I build libMyProject.a the libPods.a is linked and included, so that if I distribute libMyProject.a and other people integrates it in a project which uses some of the same dependencies it will have duplicate symbols issues.
How can I link against the libPods.a lib but not include it in libMyProject.a? It should work just like linking with other existing frameworks.
Thanks!
I solved it by removing the libPods.a lib from the section "Link Binary With Libraries" in Build Phases.
Whilst manually removing the libPods.a from the "Link Binary with Libraries" build phase does indeed work, the real answer is to not let it get added there in the first place.
The reason it is added is because the pod install command is finding the static library target as one of its targets to link with. This could be because it is the first target in the list (cocoapods' implementation causes it to pick the first one if you haven't explicitly specified targets) or it could be because you have explicitly stated it in the 'link_with' section.
The answer I find is to use the link_with section of the Podfile to explicitly state your targets, and omit the static library target.
The pods project is still created, and your dependencies brought into there as you'd expect, but the libPods.a isn't added to the build phase of your static library.
The only problem is what to put into the link_with section, if not your static library. If you have other targets that you do want to link with (an iPhone app target for instance) that's a good choice. But if your only real target is your static library, you need a little workaround.
My successful strategy so far has been to create a static library target (yes, a separate one from your main static library) and call it "Dummy". Specify this target in your Podfile's link_with section.
It is a little distasteful, granted, but it does work.
platform :ios, '5.1.1'
link_with ['Dummy']
pod 'AFNetworking', '= 1.3.1'
Referenced libraries are not (by default) included in the static library product. The linker conflict you're seeing is more likely the result of both your static library and the client app both using the default (implicit) Pod target.
Every Cocoapods-generated target includes a "Pods-target-dummy.m" file that is compiled into the product; if you use the default Pods target, it's just called "Pods-dummy.m". When both the library and client use the default target, the identical symbols produced from compiling the dummy files will cause a link error.
I tried a variation of Craig's answer myself, and found that the link_with statement is also responsible for hooking up the xcconfig generated by Cocoapods, which provides the compiler flags that control the header search path. You can add the xcconfig (or the header search path project settings) manually, of course, but I went looking for a repeatable solution for my team.
My solution is to create an explicit target for the library, with a name that is unlikely to cause conflicts with a client project (for example, the name of the library):
target 'XYZLibrary' do
pod 'AFNetworking', '2.5.2'
...
end
You can include a link_with statement within the target block if the name of the static library target (in your Xcode project) is different, but if there's only one target, I usually prefer to use the same name in both places, making link_with unnecessary.
If you have a unit test target, create two separate targets. (I currently def a set of common pods that are used in both targets, since abstract targets are not currently an option, but they may be one day.) It looks like this:
def common_pods
pod 'AFNetworking', '2.5.2'
end
target 'XYZLibrary' do
common_pods
end
target 'XYZLibraryTests' do
common_pods
end
The key is to not have any pod elements in the root of the Podfile, so that Cocoapods won't generate a default target. That way, each product gets a unique "Pods-target-dummy.m", and there's no conflict when those object files are linked together.

Resources