28

I have an app (let's call it MyApp) written in Swift with the following targets :

  • MyApp : the main target
  • MyAppKit : a target building a framework for code that is shared between the app and its extension(s), mainly the API backend and database handling
  • MyAppWidget : a Today View Widget (or whatever it's called now) which uses the MyAppKit framework.

The MyAppKit framework is linked into each target that uses it, namely MyApp and MyAppWidget. Enter Cocoapods : I used to have the following Podfile structure :

platform :ios, '8.0'
use_frameworks!

target 'MyApp' do
    # Mostly UI or convenience pods
    pod 'Eureka', '~> 2.0.0-beta'
    pod 'PKHUD', '~> 4.0'
    pod '1PasswordExtension', '~> 1.8'
end

target 'MyAppKit' do
    # Backend pods for networking, storage, etc.
    pod 'Alamofire', '~> 4.0'
    pod 'Fuzi', '~> 1.0'
    pod 'KeychainAccess', '~> 3.0'
    pod 'RealmSwift', '~> 2.0'
    pod 'Result', '~> 3.0'
end

target 'MyAppWidget' do
    # Added here event though the target only imports MyAppKit but it worked
    pod 'RealmSwift', '~> 2.0'
end

The aim here was to expose only the MyAppKit framework to the other parts and not all its pods (e.g. I don't want to be able to import Alamofire inside the main app). However, starting with the Cocoapods 1.2.0 RCs, pod install failed with the following error : [!] The 'Pods-MyApp' target has frameworks with conflicting names: realm and realmswift.. It used to work because the pods were declared for the extension but only embedded in the host app (see this issue for more info). So I removed the pods from the widget's target, leaving me with just a blank target 'MyAppWidget' line.

With this configuration, pod install runs fine but compiling fails at the linking stage for the MyAppWidget target : ld: framework not found Realm for architecture x86_64. This can be fixed by explicitly adding both Realm.framework and RealmSwift.framework to the "Link Binary With Libraries" section and the following build setting in the target's Pods-MyAppWidget.[debug/release].xcconfig :

FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Realm" "$PODS_CONFIGURATION_BUILD_DIR/RealmSwift"`

However, whenever I run pod install, the build settings are naturally reverted and I have to add the build settings again.

I see the following solutions :

  • Add a post_install hook adding these settings each time but it does seem "hacky" and after a few misguided tries, I have found no API reference and don't know how to add these settings to the MyAppWidget target through script.
  • Change the Podfile to the following structure (or even wrapping it in an abstract target) :

    [...]
    target 'MyAppKit' do
        # Backend pods for networking, storage, etc.
        pod 'Alamofire', '~> 4.0'
        pod 'Fuzi', '~> 1.0'
        pod 'KeychainAccess', '~> 3.0'
        pod 'RealmSwift', '~> 2.0'
        pod 'Result', '~> 3.0'
    
        target 'MyAppWidget' do
            inherit! :search_paths # Because else we get the "conflicting names" error
        end
    end
    

    Which seems logical to me in the sense of "the widget should know where to look during linking but doesn't need the pods per se" but this doesn't add the aforementioned build settings (I probably misunderstand the :search_paths inheritance) (edit: it actually works, but not with an abstract target). This idea came to me because in older versions of CocoaPods, the solution was apparently to add link_with, which is now deprecated.

  • Expose Realm also in the MyApp target, however this conflicts with my goal of not having access to "backend" code in the main code (it might be purely esthetic?).

So, here's my question : what's the best way to integrate pods in a framework shared between the main app and the extension while still being able to compile, without tweaking around and manually adding stuff?

Cheers and thanks in advance!


EDIT

Following Prientus' comment I've explored the possibilities of abstraction and inheritance. The underlying issues I've now uncovered are actually manifold :

  • It used to work before Cocoapods 1.2.0 because pods declared under the widget's target were embedded inside the host app yet still linked to the widget. No it simply refuses to have pods with the same name for different targets in a "main vs extension" relationship
  • Using abstract targets is insufficient because targets can't inherit only the search paths (inherit! :search_paths) from an abstract target.
  • Search paths can be inherited from a real target like MyAppKit, but this exposes all these pods to MyApp's code (which I want to avoid), and there still is the issue of linking the Realm framework (because actually the widget uses the tiniest bit of a getter and therefore needs it).

Using this last option and manually linking Realm.framework works but is suboptimal regarding my intents and what used to work. Some of these issues seem to be a bug according to various issues on Cocoapods' GitHub. I've added my own issue and will update when I have news.

Community
  • 1
  • 1
Jonas Zaugg
  • 3,005
  • 2
  • 15
  • 21
  • You mentioned using an abstract class in your solutions but I was wondering if you actually tried that. You have a very good question posed here that I have ran into myself. – Prientus Feb 08 '17 at 17:43
  • Also, I would try using the inherit! method but have the target declared outside the parent. I noticed that syntax here: https://guides.cocoapods.org/syntax/podfile.html#target and thought it might be useful. – Prientus Feb 08 '17 at 17:45

3 Answers3

24

So, what gives :

  • My concern of "separating pods between targets" is absurd because you can still import them anywhere.
  • The "you have to manually link" issue is fixed by an easy import RealmSwift statement.

The fixed and working Podfile therefore is :

platform :ios, '8.0'
use_frameworks!

target 'MyApp' do
    pod 'Eureka', '~> 2.0.0-beta'
    pod 'PKHUD', '~> 4.0'
    pod '1PasswordExtension', '~> 1.8'
end

target 'MyAppKit' do
    pod 'Fuzi', '~> 1.0'
    pod 'RealmSwift', '~> 2.0'
    pod 'Alamofire', '~> 4.0'
    pod 'KeychainAccess', '~> 3.0'
    pod 'Result', '~> 3.0'

    target 'MyAppWidget' do
        inherit! :search_paths
    end
end

And that's it. I would say that the old behaviour was more obvious and didn't require reading up on "podfile target inheritance". I did learn a lot though. Cheers!

Jonas Zaugg
  • 3,005
  • 2
  • 15
  • 21
  • I did as you said and use `inherit! :search_paths` which works only when building to a device. When I try and archive the app it fails "No such module" for every pod I try and use in the extensions – YichenBman Apr 10 '17 at 22:55
  • @YichenBman I've just archived my app without any errors. Maybe you could show me your Podfile or open a new question here. As a general rule, make sure CocoaPods and Xcode are up to date. – Jonas Zaugg Apr 12 '17 at 18:13
  • Thanks a lot man, you saved me hours of hard working, however, the archive doesn't work. It doesn't recognise the module when I import it ... – Eduard Oct 19 '17 at 17:47
  • @Eduard Not sure I understand. So you can build fine under normal circumstances but it only fails for archive? Could you provide me with your Podfile or a sample project displaying the issue? – Jonas Zaugg Oct 19 '17 at 23:06
  • I use a private framework and the Podfile is like yours, it works well when I build, but it fails when I set the configuration to Testing instead of Debug or when I try to archive it ¯\_(ツ)_/¯. – Eduard Oct 20 '17 at 07:22
  • The framework is not recognised when I import it in the Prefix.pch file, if I import it directly in the file I use it, it works. – Eduard Oct 24 '17 at 06:28
  • I've just tested building my project with Testing and it worked. Haven't tried archiving yet. I don't have any private frameworks and I have never seen a Prefix.pch file. Therefore, I'm guessing your issue comes from one of those two things. Do you use both Swift and ObjC (because I don't)? – Jonas Zaugg Oct 24 '17 at 10:28
  • Yes, I use both. I think the problem is that my project still uses precompiled header files, I'll get rid of them and come back to you, thanks a lot again :D. – Eduard Oct 24 '17 at 17:01
  • After getting rid of precompiled header files and importing my framework everywhere I managed to make this work. Thanks a lot! – Eduard Oct 25 '17 at 10:39
  • i am try this but not working pls help me. i am put question on this point https://stackoverflow.com/questions/53607730/please-add-the-host-targets-for-the-embedded-targets-to-the-podfile-pls-help-me – Amul4608 Dec 04 '18 at 07:28
7

I don't know you. But for me, it's totally legit and reasonable to have the extension and the host app contain all the pods that a framework defines. And this is what i mean:

def shared_pods
    pod 'Alamofire'
end

target 'Framework' do
    shared_pods
end

target 'Host' do
    shared_pods
    // Some other pods
end

target 'Extension' do
    shared_pods
end

I know you are worried about but if you think about it, all those 3rd party frameworks you use, they all have dependencies. You don't have to worries about them because Cocoapods takes care of them for you. If you want to utilise that, then you'll need to put a local pod entry in the list.

target 'Host' do
    pod Framework, :path => '../Framework'
end

But then you have to maintain the podspec file.

J.Wang
  • 1,136
  • 6
  • 12
  • After mind numbing research and testing for nothing I've found the solution to my issue (posted below for reference). It mostly involves the realisation that however you configure your Podfile, all pods can be imported everywhere (I guess because of the linked shared framework) and therefore my inital concern is a non-issue. It should be noted that your solution wouldn't cause the "target has frameworks with conflicting names" because you end up referencing the pod inside an extension. – Jonas Zaugg Feb 09 '17 at 22:52
  • i am try this but not working pls help me. i am put question on this point https://stackoverflow.com/questions/53607730/please-add-the-host-targets-for-the-embedded-targets-to-the-podfile-pls-help-me – Amul4608 Dec 04 '18 at 07:28
2

This is a swift-3.0 project's profile example.

platform :ios, '8.0'

def import_public_pods

  pod 'SwiftyJSON'

end


target 'Demo' do
  use_frameworks!

  # Pods for Demo
  import_public_pods 
  pod 'Fabric'
  pod 'Crashlytics'

  target 'DemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'DemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end


target 'DemoKit' do
  use_frameworks!

  # Pods for DemoKit
  import_public_pods
  pod 'RealmSwift'

  target 'DemoKitTests' do
    inherit! :search_paths
    # Pods for testing
  end

end
YorkFish
  • 77
  • 7
  • i am try this but not working pls help me. i am put question on this point https://stackoverflow.com/questions/53607730/please-add-the-host-targets-for-the-embedded-targets-to-the-podfile-pls-help-me – Amul4608 Dec 04 '18 at 07:28