I am Using cocoaLumberjack logging framework for iOS logging.
For storing logs in a file I used this code.
DDFileLogger* fileLogger = [[DDFileLogger alloc] init];
fileLogger.rollingFrequency = 60 * 60 * 24;
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
[DDLog addLogger:fileLogger];
DDLogVerbose(#"hello");
NSLog(#"hihihihihi");
I am unable to find where exactly the logfile generated by this code is stored.
Can someone help me with this problem ?
You can download the log files from connected device, or you can send directly from app. Both approaches are described below.
Send log files from app through email, in Swift
Write this in the class where you have a reference to DDFileLogger. I would put this in a custom logger class e.g. MyLogger.swift
var ddFileLogger: DDFileLogger!
var logFileDataArray: [NSData] {
get {
let logFilePaths = ddFileLogger.logFileManager.sortedLogFilePaths() as! [String]
var logFileDataArray = [NSData]()
for logFilePath in logFilePaths {
let fileURL = NSURL(fileURLWithPath: logFilePath)
if let logFileData = try? NSData(contentsOfURL: fileURL, options: NSDataReadingOptions.DataReadingMappedIfSafe) {
// Insert at front to reverse the order, so that oldest logs appear first.
logFileDataArray.insert(logFileData, atIndex: 0)
}
}
return logFileDataArray
}
}
Then, when user taps on a button to indicate that they want to send the logs,
// Required by MFMailComposeViewController
import MessageUI
#IBAction func writeEmailTapped(sender: AnyObject) {
if MFMailComposeViewController.canSendMail() {
let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self
// Configure the fields of the interface.
composeVC.setToRecipients(["your-email#company.com"])
composeVC.setSubject("Feedback for app")
composeVC.setMessageBody("", isHTML: false)
let attachmentData = NSMutableData()
for logFileData in MyLogger.sharedInstance.logFileDataArray {
attachmentData.appendData(logFileData)
}
composeVC.addAttachmentData(attachmentData, mimeType: "text/plain", fileName: "diagnostic.log")
self.presentViewController(composeVC, animated: true, completion: nil)
} else {
// Tell user about not able to send email directly.
}
}
This results in a compose email pop-up with an attachment file named diagnostic.log, which is all the log files concatenated together.
Special thanks - This is pretty much a Swift translation from the Objective-C version given by the other answer.
Get log file(s) from device directly, through USB cable
If you want to get the log files that your app created while running on device,
Connect your device to your mac
In Xcode, go to Window -> Devices
On top-left in the device list, click on the connected device.
In the main panel, under Installed Apps section, click on the application in which you ran CocoaLumberjack.
At the bottom of the Installed Apps list, click on the gear icon and then Download Container.
In Finder, right click (show menu) on the saved .xcappdata file and select Show Package Contents
Log files are saved in /AppData/Library/Caches/Logs/
Up-vote would be nice if this is helpful to you!
The answers here don't seem to account for the fact that there may be multiple log files. You can use your DDFileLogger instance's logFileManager property to loop through file information. Check out DDFileLogger.h for public methods and properties. The following may be of use:
- (NSString *)logsDirectory;
- (NSArray *)unsortedLogFilePaths;
- (NSArray *)unsortedLogFileNames;
- (NSArray *)unsortedLogFileInfos;
- (NSArray *)sortedLogFilePaths;
- (NSArray *)sortedLogFileNames;
- (NSArray *)sortedLogFileInfos;
Here is my solution for getting log data (and emailing it). Note that the default number of log files is 5 as of this writing.
- (NSMutableArray *)errorLogData {
NSUInteger maximumLogFilesToReturn = MIN([KRLogManager sharedInstance].fileLogger.logFileManager.maximumNumberOfLogFiles, 10);
NSMutableArray *errorLogFiles = [NSMutableArray arrayWithCapacity:maximumLogFilesToReturn];
DDFileLogger *logger = [KRLogManager sharedInstance].fileLogger;
NSArray *sortedLogFileInfos = [logger.logFileManager sortedLogFileInfos];
for (int i = 0; i < MIN(sortedLogFileInfos.count, maximumLogFilesToReturn); i++) {
DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:i];
NSData *fileData = [NSData dataWithContentsOfFile:logFileInfo.filePath];
[errorLogFiles addObject:fileData];
}
return errorLogFiles;
}
- (void)composeEmailWithDebugAttachment {
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
NSMutableData *errorLogData = [NSMutableData data];
for (NSData *errorLogFileData in [self errorLogData]) {
[errorLogData appendData:errorLogFileData];
}
[mailViewController addAttachmentData:errorLogData mimeType:#"text/plain" fileName:#"errorLog.txt"];
[mailViewController setSubject:NSLocalizedString(#"Good Subject", #"")];
[mailViewController setToRecipients:[NSArray arrayWithObject:#"some#email.com"]];
[self presentModalViewController:mailViewController animated:YES];
}
else {
NSString *message = NSLocalizedString(#"Sorry, your issue can't be reported right now. This is most likely because no mail accounts are set up on your mobile device.", #"");
[[[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:NSLocalizedString(#"OK", #"") otherButtonTitles: nil] show];
}
}
If you're using CocoaLumberjack, you have DDFileLogger.h and you can expose the currentLogFileInfo: method that is implemented already:
#interface DDFileLogger : DDAbstractLogger <DDLogger>
...
- (DDLogFileInfo *)currentLogFileInfo;
#end
Then, you can programmatically access the path to the current file with:
// configure logger
DDFileLogger *fileLogger = [DDFileLogger new];
[DDLog addLogger:fileLogger];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
DDLogInfo(#"log file at: %#", [[fileLogger currentLogFileInfo] filePath]);
Which, on my iPhone, printed:
log file at: /var/mobile/Applications/3BE1219F-78BE-491C-B68C-74D6FA0C2EF1/Library/Caches/Logs/log-5D1286.txt
to both the console and the file.
You can control where it is stored, for example, I sometime store it in the iTunes folder for easy retrieval. Use this in the AppDelegate when setting up the fileLogger:
NSString * applicationDocumentsDirectory = [[[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] path];
DDLogFileManagerDefault *documentsFileManager = [[DDLogFileManagerDefault alloc]
initWithLogsDirectory:applicationDocumentsDirectory];
DDFileLogger *fileLogger = [[DDFileLogger alloc]
initWithLogFileManager:documentsFileManager];
Found this to be the latest:
DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
fileLogger.logFileManager.logsDirectory;//THIS
From the official code:
Default log file manager.
All log files are placed inside the logsDirectory. If a specific
logsDirectory isn't specified, the default directory is used. On
Mac, this is in ~/Library/Logs/. On iPhone, this
is in ~/Library/Caches/Logs.
Log files are named "log-.txt", where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF].
Archived log files are automatically deleted according to the maximumNumberOfLogFiles property.
Got the answer
It is stored in
Library/Appication Support/Iphone Simulator/#version no#/applications/#your application#/documents/logs/log-3hex no>
Send log files from app through email, in Objective-C
Objective-C code:
In you header:
#import <MessageUI/MessageUI.h>
#import "DDLog.h"
#import "DDFileLogger.h"
In your implementation:
- (NSMutableArray *)errorLogData {
DDFileLogger *ddFileLogger = [DDFileLogger new];
NSArray <NSString *> *logFilePaths = [ddFileLogger.logFileManager sortedLogFilePaths];
NSMutableArray <NSData *> *logFileDataArray = [NSMutableArray new];
for (NSString* logFilePath in logFilePaths) {
NSURL *fileUrl = [NSURL fileURLWithPath:logFilePath];
NSData *logFileData = [NSData dataWithContentsOfURL:fileUrl options:NSDataReadingMappedIfSafe error:nil];
if (logFileData) {
[logFileDataArray insertObject:logFileData atIndex:0];
}
}
return logFileDataArray;
}
- (void)composeEmailWithDebugAttachment {
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
NSMutableData *errorLogData = [NSMutableData data];
for (NSData *errorLogFileData in [self errorLogData]) {
[errorLogData appendData:errorLogFileData];
}
[mailViewController addAttachmentData:errorLogData mimeType:#"text/plain" fileName:#"filename.log"];
[mailViewController setSubject:NSLocalizedString(#"LogFile Subject", #"")];
[mailViewController setToRecipients:[NSArray arrayWithObject:#"email#email.com"]];
[self presentViewController:mailViewController animated:YES completion:nil];
} else {
NSString *message = NSLocalizedString(#"Sorry, your issue can't be reported right now. This is most likely because no mail accounts are set up on your mobile device.", #"");
[[[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:NSLocalizedString(#"OK", #"") otherButtonTitles: nil] show];
}
}
- (void)mailComposeController:(MFMailComposeViewController *)mailer didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[self becomeFirstResponder];
[mailer dismissViewControllerAnimated:YES completion:nil];
}
Here is how you could add something in your Log-file:
DDLogError(#"This is an error.");
DDLogWarn(#"This is a warning.");
DDLogInfo(#"This is just a message.");
DDLogVerbose(#"This is a verbose message.");
Don't forget to set your ddLogLevel in your Constants-file.
Constants.h :
extern NSUInteger const ddLogLevel;
Comstants.m :
NSUInteger const ddLogLevel =
#ifdef DEBUG
LOG_LEVEL_VERBOSE;
#else
LOG_LEVEL_ERROR;
#endif
It is very obvious but don't forget to configure CocoaLumberjack in your AppDelegate.m file.
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
[fileLogger setMaximumFileSize:(1024 * 1024)];
[fileLogger setRollingFrequency:(3600.0 * 24.0)];
[[fileLogger logFileManager] setMaximumNumberOfLogFiles:7];
[DDLog addLogger:fileLogger];
The best place to paste this code is - (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; in your AppDelegate.m file.
Also you should add this line of code in your AppDelegate.m header:
#import <CocoaLumberjack/CocoaLumberjack.h>
Hope it will help.
I had to rewrite it a little to be compatible with Swift 4...
var logFileDataArray: [Data] {
let logFilePaths = delegate.fileLogger.logFileManager.sortedLogFilePaths
var logFileDataArray = [Data]()
for logFilePath in logFilePaths {
let fileURL = URL(fileURLWithPath: logFilePath)
if let logFileData =
try? Data(contentsOf: fileURL, options: Data.ReadingOptions.mappedIfSafe) {
logFileDataArray.insert(logFileData, at: 0)
}
}
return logFileDataArray
}
func sendApplicationLog(text: String) {
if MFMailComposeViewController.canSendMail() {
let controller = MFMailComposeViewController()
controller.mailComposeDelegate = self
controller.setToRecipients(["dohan.rene#gmail.com"])
controller.setSubject("Log of motorkari iOS")
controller.setMessageBody(text, isHTML: false)
var attachmentData = Data()
for logFileData in logFileDataArray { attachmentData.append(logFileData) }
controller.addAttachmentData(attachmentData, mimeType: "text/plain",
fileName: "motorkari_ios_application.log")
present(controller, animated: true, completion: nil)
} else {
showMessage("Log cannot be send !")
}
}
I don't know if this might help somebody else... I took the previous answers and passed it to Swift 5. Apart from getting all the paths, it prints the content.
let logFileLogger = DDFileLogger()
print(logFileLogger.logFileManager.logsDirectory)
for path in logFileLogger.logFileManager.sortedLogFilePaths {
do {
let content = try String(contentsOfFile: path, encoding: .utf8)
print(content)
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
Related
I am trying to integrate Hyperpay payment into React Native project and I have problems with objective-c, I followed an article and found many issues and with searching, I solve them, but still two issues I can't solve because I am not familiar with objective-c
Issue 1,
No known class method for selector 'presentCheckoutForSubmittingTransactionCompletionHandler:cancelHandler:'
Issue 2,
No known class method for selector 'dismissCheckoutAnimated:completion:'
I am sorry if my code is long but I don't to miss something
// RCTCalendarModule.m
#import "HyperPay.h"
#import "UIKit/UIKit.h"
#import <OPPWAMobile/OPPWAMobile.h>
#implementation HyperPay{
RCTResponseSenderBlock onDoneClick;
RCTResponseSenderBlock onCancelClick;
UIViewController *rootViewController;
NSString *isRedirect;
OPPPaymentProvider *provider;
}
// To export a module named RCTCalendarModule
RCT_EXPORT_METHOD(openHyperPay:(NSDictionary *)indic createDialog:(RCTResponseSenderBlock)doneCallback createDialog:(RCTResponseSenderBlock)cancelCallback) {
onDoneClick = doneCallback;
onCancelClick = cancelCallback;
NSArray *events = #[];
if ([indic[#"is_sandbox"] isEqualToString:#"1"]) {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];
} else {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeLive];
}
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
// Set available payment brands for your shop
checkoutSettings.paymentBrands = #[#"VISA", #"MASTER"];
// Set shopper result URL
checkoutSettings.shopperResultURL = #"com.simicart.enterprise.payments://result";
OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider checkoutID:indic[#"checkoutId"]
settings:checkoutSettings];
dispatch_async(dispatch_get_main_queue(), ^{
[OPPCheckoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
if (error) {
// Executed in case of failure of the transaction for any reason
if (isRedirect && ![isRedirect isEqualToString:#"1"]) {
onCancelClick(#[#"cancel", events]);
}
} else if (transaction.type == OPPTransactionTypeSynchronous) {
// Send request to your server to obtain the status of the synchronous transaction
// You can use transaction.resourcePath or just checkout id to do it
NSDictionary *responeDic = #{#"resourcePath" : transaction.resourcePath};
onDoneClick(#[responeDic, events]);
NSLog(#"%#", transaction.resourcePath);
} else {
// The SDK opens transaction.redirectUrl in a browser
// See 'Asynchronous Payments' guide for more details
}
} cancelHandler:^{
onCancelClick(#[#"cancel", events]);
// Executed if the shopper closes the payment page prematurely
}];
});
}
- (instancetype)init{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getStatusOder:) name:#"getStatusOrder" object:nil];
}
return self;
}
- (void)getStatusOder:(NSNotification*)noti{
[OPPCheckoutProvider dismissCheckoutAnimated:YES completion:^{
isRedirect = #"1";
NSURL *url = noti.object;
NSString *urlString = [url absoluteString];
NSLog(#"%#", urlString);
if (![urlString isEqualToString:#"com.simicart.enterprise.payments://result"]) {
NSArray *events = #[];
NSDictionary *responeDic = #{#"url" : urlString};
onDoneClick(#[responeDic, events]);
}
}];
}
#end
I want to allow document(pdf, doc, docx) selection in my app. And I want to integrate iCloud for this. All I want to do is to open the iCloud drive from my app and there user can select the file and returns back to the origin app. Something like whatsapp has done for document selection in iOS app.
Any idea regarding this?
Read this below link you will get idea: https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/DocumentPickerProgrammingGuide/AccessingDocuments/AccessingDocuments.html
Below is the code :
-(IBAction)iCloudDriveFullFolder:(id)sender{
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:#[#"public.data"]
inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
}
#pragma mark - iCloud files
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
// NSString *alertMessage = [NSString stringWithFormat:#"Successfully imported %#", [url lastPathComponent]];
//do stuff
}
}
I have an in-home app that will be supported by IT team.
When they support desktop apps, they may read logs to find any troubles (like server returned 404 on sync request).
I can use NSLog for iOS, but how can user access them with out of Xcode?
I can't ask any user "please give me your phone to investigate what has happened".
Does there is some tool any IT person with out of Xcode and Mac may use to read iOS logs?
Does there is some tool any IT person with out of Xcode and Mac may use to read iOS logs?
Unfortunately not. It used to be that you could run an app on your device that would read the Console log, but Apple took that ability away; I guess they saw it as a security breach.
If your user can get to a Mac running Xcode, they can view the console log directly in Xcode.
Otherwise, as others have suggested, you will have to build into your app the capacity to keep a log in a place you can get to. For example you can write to a file and then offer (within the app) to email that file to yourself. Many apps have an interface to a facility like this in their Settings bundle.
I've been using the combination of CocoaLumberjack, Antenna & DDAntennalogger at work for remote logging. Basically, you have to set up an end-point at your server and Antenna will be used to send the logs remotely.
Here's the reference that when configuring it on my project:
Remote logging using CocoaLumberjack, Antenna & DDAntennaLogger
This is how you can do it:
Step 1: Redirect your NSLog statements to a text file in file system. This you can do on specific user action or always enable it and delete it periodically.
Step 2: Have a web service which will allow you to upload the saved logs in the file system. You could trigger this on user action or may be a timer based job.
Step 3: Delete the logs from file system once upload is successful.
Here is a example of such a custom logger:
#import "MyCustomLogging.h"
#define kMyCustomLoggingFile #"NSLogging.txt"
static NSString *const kMyDeviceLogUploadURL = #"uploadDeviceLogURL";
#interface MyCustomLogging ()
#property (nonatomic, strong) MyRequestHandler *requestHandler;
#property (nonatomic, assign, getter = isNsLogRedirected) BOOL nsLogRedirected;
#property (nonatomic, assign) BOOL shouldStopLogging;
#property (nonatomic, strong) NSString *pathForLogging;
#end
#implementation MyCustomLogging
static int savedStdErr = 0;
static MyCustomLogging *sharedMyCustomLogging = nil;
+ (MyCustomLogging *)sharedMyCustomLogging {
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
sharedMyCustomLogging = [[self alloc] init];
});
return sharedMyCustomLogging;
}
#pragma mark -
#pragma mark Start Method
- (void)startLogging {
// Starting the Redirection of the Logs
if (!self.isNsLogRedirected) {
[self nsLogRedirectedToFile];
}
}
#pragma mark -
#pragma mark Stop Method
- (void)stopLogging {
NSLog(#"Stopping the logging");
NSString *aLoggingPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.pathForLogging = [aLoggingPath stringByAppendingPathComponent:kMyCustomLoggingFile];
// If file already exists & logging was not redirected then directly upload the logs::A Possible case of app quit/crash without uploading previous logs
if ([self isLogFilePresent] && !self.nsLogRedirected) {
[self uploadLogs];
} else if (self.isNsLogRedirected) { //Check for Successive Stop Notifications
self.shouldStopLogging = YES;
[self restoreNSLog];
} else {
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueOne};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}
}
#pragma mark -
#pragma mark Private Method
- (void)nsLogRedirectedToFile {
if (!self.isNsLogRedirected) {
NSLog(#"Redirecting NSLogs to a file.....");
self.nsLogRedirected = YES;
savedStdErr = dup(STDERR_FILENO);
NSString *aLoggingPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.pathForLogging = [aLoggingPath stringByAppendingPathComponent:kMyCustomLoggingFile];
NSLog(#"Logging Path: %#", self.pathForLogging);
freopen([self.pathForLogging cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
NSLog(#"NSLog Redirected to a file Succesfully");
[MySessionObject setLoggingOn:YES];
}
}
- (void)restoreNSLog {
if (self.isNsLogRedirected) {
[MySessionObject setLoggingOn:NO];
NSLog(#"NSLog Will be Restored now....");
self.nsLogRedirected = NO;
fflush(stderr);
dup2(savedStdErr, STDERR_FILENO);
close(savedStdErr);
savedStdErr = 0;
}
[self uploadLogs];
NSLog(#"NSLog Restored Successfully");
}
- (void)uploadLogs {
NSLog(#"Now uploading files");
// Disable logging before files are uploading
MySessionObject.enableLogging = NO;
NSError *anError = nil;
NSData *aLogData = [NSData dataWithContentsOfFile:self.pathForLogging options:NSDataReadingUncached error:&anError];
//Converting to String
NSString *aLogString = [[NSString alloc] initWithData:aLogData encoding:NSUTF8StringEncoding];
NSMutableDictionary *aPostBody = [[NSMutableDictionary alloc] initWithCapacity:3];
[aPostBody setValue:aLogString forKey:#"logData"];
[aPostBody setValue:MySessionObject.wifiMACAddress forKey:#"deviceMACAddress"];
[aPostBody setValue:MySessionObject.deviceToken forKey:#"deviceID"];
__weak MyCustomLogging *aBlockSelf = self;
self.requestHandler = [[MyRequestHandler alloc] initWithEndPoint:#"/uploadLogs" body:aPostBody container:nil loadingOverlayTitle:nil successHandler:^(NSDictionary *iResponse) {
if (iResponse) {
//Remove the File From the Path
NSError *aFileError = nil;
BOOL aFileRemoveSuccess = [[NSFileManager defaultManager] removeItemAtPath:self.pathForLogging error:&aFileError];
if (!aFileRemoveSuccess) {
//Tracking the Event
NSString *aDescription = [NSString stringWithFormat:#"Error Code:%ld Error Description:%#", (long)[aFileError code], [aFileError localizedDescription]];
NSLog(#"Error occured while deleting log file:%#", aDescription);
}
// Clearing all
aBlockSelf.pathForLogging = nil;
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueOne};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}
} andErrorHandler:^(NSString *iMessage, NSString *iKey, NSInteger iErrorCode, BOOL iIsNetworkError) {
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueZero};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}];
[self.requestHandler executeRequest];
}
- (BOOL)isLogFilePresent {
NSFileManager *aFileManager = [[NSFileManager alloc] init];
BOOL aFilePresent = [aFileManager fileExistsAtPath:self.pathForLogging];
return aFilePresent;
}
#end
I am using the following code to call action sheet sharing in my app:
- (IBAction)sendPost:(id)sender
{
NSArray *activityItems = nil;
UIImage *appIcon = [UIImage imageNamed:#"appIcon.png"];
NSString *postText = [[NSString alloc] initWithFormat:#"LETS ASSUME THIS STRING IS LONGER THAN 140 CHARACTERS THAT TWITTER PROHIBITS BUT CAN STILL BE SHARED VIA FACEBOOK, EMAIL, TEXT"];
activityItems = #[postText,appIcon];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
The problem is this: postText is longer than 140 characters, so sharing via twitter will not be possible, the character count will be -x (red number of characters you are over in order to share via twitter), my question is this: How can I make an exception so that a different message say shortPostText will be the one used when twitter is selected for sharing?
And once the sendPost action is sent I don't see a way to explicitly set a string for twitter, once you are here:
Edit: I dont understand why someone would down-vote this question, I am not asking how to make an if/else statement or how to program. This is a genuine question, that needs a genuine answer.
UPDATE: I need a work around this because this is what I get when a user tries to share via twitter in my app:
A red/negative character indicator and a non-active post button, so unless that character count goes down to 0 or less it will not allow the post to go to twitter.
TL;DR Use UIActivityItemSource to special case payload depending on what the user selection was.
Try this instead:
- (IBAction)sendPost:(id)sender
{
UIImage *appIcon = [UIImage imageNamed:#"appIcon.png"];
NSString *postText = [[NSString alloc] initWithFormat:#"LETS ASSUME THIS STRING IS LONGER THAN 140 CHARACTERS THAT TWITTER PROHIBITS BUT CAN STILL BE SHARED VIA FACEBOOK, EMAIL, TEXT"];
TextItemSource *itemSource = [[TextItemSource alloc] initWithString:postText previewImage:appIcon];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:#[itemSource] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
// ------- TextItemSource.h
#interface TextItemSource : NSObject <UIActivityItemSource>
- (id)initWithString:(NSString *)string previewImage:(UIImage *)previewImage;
#end
// ------- TextItemSource.m
#implementation TextItemSource
{
NSString *_string;
UIImage *_previewImage;
}
- (id)initWithString:(NSString *)string previewImage:(UIImage *)previewImage
{
self = [super init];
if (self) {
_string = [string copy];
_previewImage = previewImage;
}
return self;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return _string;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
NSString *string = _string;
if ([activityType isEqual:UIActivityTypePostToTwitter]) {
#pragma mark TODO: do smarter thing :)
string = [_subject substringToIndex:140];
}
return string;
}
- (UIImage *)activityViewController:(UIActivityViewController *)activityViewController thumbnailImageForActivityType:(NSString *)activityType suggestedSize:(CGSize)size
{
// might want to scale image to fit suggestedSize
return _previewImage;
}
#end
i want to migrate my existing app to iCloud.
in my attempt to learn what to do, i have tried a sample app that has worked as described in the demo (as seen in lecture 17 of the iTunes stanford university course).
can someone who has had this problem help me diagnose what step i messed up to cause the following to fail? is this just telling me basically that there's no data to query? or is there something else going on that i haven't properly configured?
i am getting the following in my console (which occurs in the code below immediately after i call [self.iCloudQuery startQuery];
item update error: 0, Error Domain=LibrarianErrorDomain Code=10 "The operation couldn’t be completed.
(LibrarianErrorDomain error 10 - Unable to configure the collection.)" UserInfo=0x11ee00 {NSDescription=Unable to
configure the collection.}
i created a development provisioning profile that has iCloud turned on on iTunes connect.
i turned on entitlements. here are the contents:
the code is straight from the Stanford University iTunes lectures:
#import "DocumentViewController.h"
#interface DocumentViewController ()
#property (strong, nonatomic) NSArray* documents; // of NSURLs
#property (strong, nonatomic) NSMetadataQuery* iCloudQuery;
#end
#implementation DocumentViewController
#synthesize documents = _documents;
#synthesize iCloudQuery = _iCloudQuery;
- (void)setDocuments:(NSArray*)documents {
// always sort documents alphabetically
documents
= [documents sortedArrayUsingComparator:^NSComparisonResult(NSURL* url1, NSURL* url2) {
return [[url1 lastPathComponent] caseInsensitiveCompare:[url2 lastPathComponent]];
}];
if (![documents isEqualToArray:_documents])
{
_documents = documents;
[self.tableView reloadData];
}
}
- (NSMetadataQuery*)iCloudQuery {
if (!_iCloudQuery)
{
_iCloudQuery = [[NSMetadataQuery alloc] init];
_iCloudQuery.searchScopes
= [NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope];
_iCloudQuery.predicate
= [NSPredicate predicateWithFormat:#"%K like '*'", NSMetadataItemFSNameKey];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processCloudQueryResults:)
name:NSMetadataQueryDidFinishGatheringNotification
object:_iCloudQuery];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processCloudQueryResults:)
name:NSMetadataQueryDidUpdateNotification
object:_iCloudQuery];
}
return _iCloudQuery;
}
#pragma mark - private implementation
- (NSURL*)iCloudURL {
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
- (NSURL*)iCloudDocumentsURL {
return [self.iCloudURL URLByAppendingPathComponent:#"Documents"];
}
- (NSURL*)filePackageURLForCloudURL:(NSURL*)url
{
if ([url.path hasPrefix:self.iCloudDocumentsURL.path])
{
NSArray* iCloudDocumentsURLComponents = self.iCloudDocumentsURL.pathComponents;
NSArray* urlComponents = url.pathComponents;
if (iCloudDocumentsURLComponents.count < urlComponents.count)
{
NSRange newRange = NSMakeRange(0, iCloudDocumentsURLComponents.count+1);
urlComponents = [urlComponents subarrayWithRange:newRange];
url = [NSURL fileURLWithPathComponents:urlComponents];
}
}
return url;
}
- (void)processCloudQueryResults:(NSNotification*)notification
{
[self.iCloudQuery disableUpdates];
NSMutableArray* documents = [NSMutableArray array];
NSUInteger resultCount = self.iCloudQuery.resultCount;
for (NSUInteger i = 0; i < resultCount ; ++i)
{
NSMetadataItem* item = [self.iCloudQuery resultAtIndex:i];
NSURL* url = [item valueForAttribute:NSMetadataItemURLKey];
url = [self filePackageURLForCloudURL:url];
[documents addObject:url];
}
self.documents = documents;
[self.iCloudQuery enableUpdates];
}
#pragma mark - overrides
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (![self.iCloudQuery isStarted])
[self.iCloudQuery startQuery];
[self.iCloudQuery enableUpdates];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.iCloudQuery disableUpdates];
[super viewWillDisappear:animated];
}
for anyone else running into this … apparently, this is the error that occurs when you haven't enabled iCloud on the device on which you are testing. once i enabled iCloud on the device, the error stopped occurring in my log.
and so now i know what error to check for in order to present the user with a dialog saying "connecting to iCloud won't work until you sign up for iCloud in the system preferences".