I have an existing app that needs to be compiled for different clients
Each client requires their own icon and splash screen.
I would also like to be able to conditionally include various features depending whether the particular client requires them or not.
I have tried setting up different targets for each client, but not having much luck so far.
The different resources with the same name, but a different path keep getting mixed up.
Ideally I would like to be able to build an app by duplicating another client that is similar and then just make the minimum number of changes to create the app for the new client.
What is the best way to set this app up?
Separate targets for each client should be the way to go. For the features, I would suggest first setting up a macro identifying the client in the target settings (under "Preprocessor Macros" on the build tab), then having a FeatureDefines.h file that looks like this:
#ifdef macroClientA // assume client A wants features 1 and 3
# define macroFeature1
# define macroFeature3
#endif
// and similarly for the other clients
Now you can use
#import featureDefines
#ifdef macroFeature1
any place you need to test if feature 1 is desired or not.
For the separate icons, your target settings can specify a different info.plist file for each client, and those files can in turn specify a different filename for the icon.
For the separate splash screens, iOS always requires the splash screen to be named Default.png, but they can go in different subdirectories of your project directory. You can control which one is used for which target by right clicking where Xcode says "Groups & Files", selecting Target Membership, then checking the checkbox for the one you want to use, and making sure the other ones are unchecked.
For resources, I would suggest naming your resource files like this:
resourceName.ext // generic resource to be used if there is no client-specific one
resourceName-clientName.ext // client-specific resource
Next set up a general resource-finder method that looks something like this:
-(NSString *) resourcePathForResourceName: (NSString *) resourceName extension: (NSString *) ext {
NSString *clientName;
#ifdef macroClientA
clientName = #"clientA";
#endif // and similarly for the other clients
NSString *clientSpecificName = [NSString stringWithFormat: #"%#-%#.%#", resourceName, clientName, ext];
NSString *genericName = [NSString stringWithFormat: #"%#.%#"];
if ([[NSFileManager defaultManager] fileExistsAtPath: clientSpecificName])
return clientSpecificName;
else if ([[NSFileManager defaultManager] fileExistsAtPath: genericName])
return genericName;
else
// handle the error
}
Running all your resource file grabs through that method will allow you to add client-specific resources to your project without changing a single line of code.
I have a similar scenario and how I handle it is as follows:
1) the core code of the app is kept in a "application_name-base" folder
2) different clients are in their "application_name-client_name" folder
3) the project file is in the client folder and includes the references from the base folder without using copy.
4) files that need to be unique to the the client's project are in the client folder. Usually images using the same name. or .h .m files that need to be unique to the individual project. Also allows for you to not include files on a project by project basis.
Keeps code central but allows for different code per client without leading to confusion.
You could make a separate target for each client, and put each clients assets in their own folder with the same name. I did something similar where I had two projects which had a lot in common and just added/removed the appropriate assets from each project.
With your scenario, at that point you can just add a new folder with the client specific resources to each target and it wouldn't require any code changes. If the paths are an issue, you could consider using the [UIImage imageNamed:] method which doesn't require the full path, just the filename.
I'd rather write some automator/scripts instead of using XCode for such duties !
http://developer.apple.com/tools/xcode/automatorforxcode.html
Write your code to allow an external configuration file to do the bulk of the setup, then include the correct assets with your targets. Having one project with a new target for each client, you can choose which assets get included with each target. If you're having trouble with the multiple targets you could use git to manage a core project, then branch it for each client.
I would just create a settings interface for a client to go in and customize the app to their company/individual needs. For example, providing the image from their Photo Library, etc.
You can make project for each customer, and include all sources from the same path, without copying them into project directory.
So you'll be able to replace icon/splash screen in a project, while using the same codebase.
This looks like the easiest way so far.
Related
I have a subproject (static library) inside my project.
As this static library may be used by a bunch of app, I have this config.h file on my project that contains the app configuration. The static library must read it.
The problem is that adding
#import "config.h"
on the static library fails, because the file cannot be found.
I could add an absolute path to my project root on the search headers, but I want to make this not hard coded because this static library will be used by other projects. Another problem is that I cannot use relative links like ../.., for example, because the static library is on another volume.
Including $(SRCROOT) on the search paths of the static library will give me the root for that library not for the project using it, that is what I want.
How do I solve that?
Just pay attention to my question. I am inside a static library that is used by a project. Config.h is out there in the project. I want to import that config.h on my static library.
If there is an easy way to do that, please tell me.
I have uploaded a sample project to here and here, so you can see my pain.
thanks
One way, somewhat of a hack, is to add a Run Script to each App's Build Phase, as the first item, and have it copy Config.h to some known place - /tmp/Config.h, and your included library will look for it there. Since the file is copied on every build, it will always be proper.
EDIT1: So not pretty, but you can add a Run Build Script to just under the Dependencies in the library. Just add one and leave the checkbox set to show environmental variables. You can see this one set:
FILE_LIST=/Volumes/Data/Users/dhoerl/Library/Developer/Xcode/DerivedData/MyProject-fdzaatzqtmrnzubseakedvxmsgul/Build/Intermediates/MyStaticLibrary.build/Debug-iphoneos/MyStaticLibrary.build/Objects/LinkFileList
What you can see is that several of these have as the prefix the current project folder:
/Volumes/Data/Users/dhoerl/Library/Developer/Xcode/DerivedData/MyProject-
You can write some script there to get the prefix, then append the local Config.h file path, and now you have a fully qualified path to the Config.h header, which you can then copy to a known location in the library. I'm going to post on the xcode forum as there may be a better solution - there use to be in Xcode 3. I'll update this if I get anything substantive back.
EDIT2: Try This:
1) Click on the library, click on Build Phases, add a Run Script Build phase by tapping bottom right '+' button
2) Drag it so its the second item in the list (below Target Dependencies)
3) Change the Shell to "/bin/ksh"
4) Paste this in, after editing it to have the proper files/paths:
# Get the Project Name (assumes upper/lower/numbers only in name)
PROJ=$(echo $BUILD_DIR | sed -En -e 's/(\/.*\/)([A-Za-z0-9]+)-([a-z]+\/Build\/Products)/\2/p')
# Use this variable to construct a full path
FULL_PATH="/Volumes/Data/Users/dhoerl/Downloads/nightmare/"$PROJ"/"$PROJ"/HelloWorldLayer.h"
echo FULL_PATH equals $FULL_PATH
# Make Sure MyStaticLibrary is correct
echo PROJ_DIR equals "$PROJECT_DIR/MyStaticLibrary"
cp -f "$FULL_PATH" "$PROJECT_DIR/MyStaticLibrary"
5) This assumes that you put the library anywhere you want, but each App project has to havethe same parent folder (not much of a restriction - what I did in the past).
PS: When I do this kind of thing, I usually don't include an actual file of the app, but create a header for a class that is not instantiated in the library. Lets call this Foo. So in your library, you have Foo.h, and it has lots of methods that return info - the number of widgets, the location of some special folder, plists, arrays, dictionaries, whatever. The idea is the library knows how to get whatever it needs through this interface (class singleton, or just a class with class methods. YMMV.
PSS: anyone else reading this, it pays to create demo projects.
I'd go a different route. Make this config.h file part of the static library using compiler symbols in it to switch features. Then in your projects define those symbols depending on what features you need.
I have an iPhone app that programmatically gets a path to the Application Support Folder, tests for a file in the application support folder, and then either loads the file or creates a new one depending on the result. This is easy and there are a ton of tutorials on how to do this.
But I can't for the life of me find anything in the ios documentation or online about how to put a file in the Application Support Folder before ever building the app. I tried creating a Library/Application Support in my apps Xcode folder to no avail.
Specifically, I am making a game, and I want to include level packs in the game's Library/Application Support folder BEFORE I build and run the application. Preferably by dragging and dropping the files in Finder. Is this possible?
#Vimal Venugopalan
EDIT:
As Vimal mentioned, I could use [[NSBundle mainBundle] pathForResource:ofType:inDirectory:] method, but this gives a path similar to "~/MyApp.app/MyFolder/MyFile.plist". That is if "~" was the path to the app's home directory. Or more specifically "~" is the path returned by calling the NSHomeDirectory(); function. Where I want to put my files is in "~/Library/Application Support/MyFolder/MyFile.plist"
I want the files in this spot because I want to incorporate level-packs into my game. I want to include some level packs with the app download, and I would eventually like to have additional downloadable level-packs. Now the downloaded level packs definitely have to go in the "~/Library/Application Support/" folder (which I know how to do programmatically), so I would like to include the original level-packs in the same place before building and running the app. It would be so much simpler to have all my level-packs in one place!
You can add these files in the Project and access these files at runtime Xcode will copy them in the Copy Bundle Resource phase. This normally copies into the root of the bundle. To deal with directories see #CocoaFu's answer to this SO question.
Then in the code
NSBundle* bundle = [NSBundle mainBundle] will give you the main bundle
From this you look in directories using pathForResource:ofType:inDirectory: e.g.
NSString* path = [bundle pathForResource:#"file.xml"
ofType:nil
inDirectory:#"a"];
The methods are given in NSBundle class reference also see the Bundle Programming guide
Hope this solves your issue. If not please comment
I am working on an email client based on ReMail. Basically, I reused the ReMail project so that the MailCore etc dependencies would be all set up out of the box so I could build a new app on top of them. I'm trying to give the app to some beta testers via ad hoc, but when I try to create an IPA, I receive this error:
xxxx does not contain a single–bundle application or contains multiple products. Please select another archive, or adjust your scheme to create a single–bundle application.
I have set Skip Install to YES for all dependencies, and moved all files under Copy Header to the "project" section, but I am still unable to build a single APP file.
I don't know if this is significant, but when I open up the xcarchive file, within the products directory, I find a the APP file under Applications, and then a file structure mirroring the absolute path to the header files for MailCore.
Any ideas as to what might be going on? I'm very near the tearing-out-chunks-of-hair-in-frustration stage, and I don't think the unnaturally bald crazy person look would suit me.
Thanks!
Try removing the “Copy Headers” phase entirely. It's only supposed to be used for frameworks, not static libraries. The Xcode static library template that includes a “Copy Headers” phase is wrong.
Check out the “Working with Schemes and Projects in Xcode” video from WWDC 2012, starting at 45m10s.
I'm creating one iphone application that uses and xml to get some data from and a few images. Lets say that originally these files will be in the application bundle. But then I want to make my application to get updates from a web service. So lets say I download a new xml and new Images.
Where do I save them? I think I will be able to save them in the application "cache" right?
But then How do I make my application check if this resource exists in the cache, then load that one...else load the one in the application bundle?
iOS to be used 4.3 but if it makes it easier we can go to 5.
You will have to use NSFileManager to save the file to and load it from the document directory. Check out the following link, which actually contains all the info you need:
http://www.friendlydeveloper.com/2010/02/using-nsfilemanager-to-save-an-image-to-or-loadremove-an-image-from-documents-directory-coding/
If [UIImage imageWithContentsOfFile:fullPath]; (fullPath being the NSString with contains the path to your image in your document directory) returns nil, then the image is not present, so you'll have to load the default image from your bundle.
You can access your default bundle files using [[NSBundle mainbundle] pathForResource:#"yourFileNameHere" ofType:#"yourTypeHere"] This will return the path to your file in your bundle and you can just pass it to [UIImage imageWithContentsOfFile:]
This example only applies to UIImage, but you can easily adapt it to use other classes. I think the UIImage example is enough for illustrative purposes though.
You'll probably want to create a versioning scheme. After the app starts (or periodically) call the web service for a very cheap call to ask what version it has. You'll know what version you have (write in a plist or have the version number in the folder structure). Another options is pushing a notification.
The next thing you'll have to think about is how the app reacts to getting a new data set. You could do it only on start up but that might block the start up experience if you're bounding it to a web call. The other option is to allow the dataset to change while the app is open. You could have a model that uses NSNotificationCenter to notify your views and controllers that the data has changed. I would probably version the storage as well (folder per) to help with the transition.
As for how, you can make web requests with something like ASIHttpRequest, NSFileManager to write to the documents directory, and plists to save settings like version.
I have a project with multiple targets each of which builds a pretty similar versions of the app but with different images assets and plists. For plists/images that's fine but I use the ShareKit and Appirater frameworks which have header files with #defines for their config. For each version I believe need to import a different version of this header file, as the config is different for each app built by each target.
So target A has SHConfig.h
and target B has a DIFFERENT SHConfig.h
I could edit the source for these frameworks to import different headers based on the target but that'd be messy when I come to upgrade the frameworks.
Is there a better way to import different header files (with the same name) based on the target?
Assuming they're in different directories, set the Header Search Paths in each target to put the correct directory first.
You may want to set it to something like $(SRCROOT)/foo:$(HEADER_SEARCH_PATHS), though I'm not sure whether that's necessary.
What I found useful was to put the Common directory name in the header search path, and then to use a different #import. My directory structure was Common/Views/v1 and Common/Views/v2. I wanted the v1 for one target and the v2 for another.
In my case, the search path I used in Header Search Paths was:
$(SRCROOT)/../Common/
Then, I used:
#import <Views/v2/ActivityIndicator.h>
In the target that needed the second version (this finds $(SRCROOT)/../Common/Views/v2/ActivityIndicator.h).
Oddly, the other target (the first one I created) is fine without specifying the full path. I.e.,
#import "ActivityIndicator.h"
works to find $(SRCROOT)/../Common/Views/v1/ActivityIndicator.h
Following process solved the issue for me
Select specific target
Under "Build Phases" --> add "New Headers Phase" --> Expand "Headers" --> click on add(plus symbol) and --> browse to the file to be added specific for the target. (It will add file under 'project' section).
Repeat the process for other targets.
Tested on Xcode 10.2