I have an app saving images to parse, it consists of a collection view in the first VC and a detail view controller where you add a picture via UIImagePickerController. I've worked with parse a bit in Objective-C before, but keep getting this error
[PFObject setPhoto:]: unrecognized selector sent to instance 0x7fa15ac8f1c0
I have a parse class in the project with the properties, I know there is a different way but I've usually done it like this and had no problems.
Parse class header file
#import <Parse/Parse.h>
#interface Picture : PFObject <PFSubclassing>
#property (nonatomic, strong) NSString *Caption;
#property (nonatomic, strong) NSArray *Likes;
#property (nonatomic, strong) PFFile *Photo;
+ (NSString *)parseClassName;
#end
.m file
#import "Picture.h"
static NSString * const ClassName = #"Picture";
#implementation Picture
#dynamic Caption;
#dynamic Likes;
#dynamic Photo;
+ (NSString *)parseClassName {
return ClassName;
}
#end
Here is where I save the image
- (IBAction)saveImage:(id)sender {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Add caption" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = #"title";
}];
[alertController addAction:[UIAlertAction actionWithTitle:#"Save" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSArray *textFields = [alertController textFields];
NSString *nameString = ((UITextField *)textFields[0]).text;
NSData *imageData = UIImagePNGRepresentation(self.chosenImage);
// PFObject *object = [PFObject objectWithClassName:#"Picture"];
Picture *picture = [Picture object];
PFFile *file = [PFFile fileWithData:imageData];
[picture saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
picture.Photo = file;
picture.Caption = nameString;
[picture saveInBackground];
[picture pinInBackground];
UIAlertController *successAlert = [UIAlertController alertControllerWithTitle:#"Photo Saved!" message:nil preferredStyle:UIAlertControllerStyleAlert];
[successAlert addAction:[UIAlertAction actionWithTitle:#"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:successAlert animated:YES completion:nil];
}
else {
NSLog(#"%#", error.localizedDescription);
}
}];
[alertController addAction:[UIAlertAction actionWithTitle:#"Okay!" style:UIAlertActionStyleCancel handler:nil]];
//save to parse here
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
//image picker delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
self.chosenImage = info[UIImagePickerControllerEditedImage];
self.imageView.image = self.chosenImage;
[picker dismissViewControllerAnimated:YES completion:nil];
}
I thought at first it could be the size of the image but when I comment that part out and just try to save the caption I get the same error but with "setCaption" rather than "setPhoto"
Update: The objects have saved to parse but there is no data in the column, the keys (properties) are spelled right and everything too.
Thanks!
The problem appears to be that the dynamically generated setters are not getting created on your custom PFObject.
The problem may be your choice of case on your properties. The convention as defined by Apple is to start a property with a lowercase letter and have it be camel case thereafter. You should change the case to lowercase, as that may be causing a conflict with the creation of the getters.
You already have
PFObject *object = [PFObject objectWithClassName:#"Picture"];
and I'm guessing that your code did not work with that.
The other thing I can suggest is to move
picture.Photo = file;
picture.Caption = nameString;
out of the block and put it after PFFile *file = [PFFile... and delete
[picture saveInBackground];
as you appear to be saving your picture object twice. From your description that corresponds to the empty record that you are getting in Parse. Therefore, that save is being successfully performed. It is the second save that is failing.
Also, try adding
#import <Parse/PFObject+Subclass.h>
to your implementation (.m) file as that is where the class method for object is defined.
Related
I have taken an NSString from one class to another to be saved on parse.com
I have a quiz view controller where a user enters a question in a UITextView and then they press next to go to the select friends view controller where they select a user and then send the question over parse.
quiz.h
#property (nonatomic,strong) IBOutlet UITextView *textField;
#property (nonatomic, strong) NSString *text;
quiz.m
- (void)viewDidLoad {
[super viewDidLoad];
UITextView *textView = [[UITextView alloc] init];
NSString *text = self.textField.text;
selectFriendsViewController *sfvc = [[selectFriendsViewController alloc] init];
sfvc.string = text;
}
- (IBAction)next:(id)sender {
if ([text length] == 0) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"Enter some text"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
} else {
}
}
selectfriendsviewcontroller.h
#property (nonatomic, strong) NSString *string;
selectfriendsviewcontroller.m
- (void)viewDidLoad {
[super viewDidLoad];
quizViewController *qvc = [[quizViewController alloc] init];
qvc.text = string;
UITextView *textfield = [[UITextView alloc] init];
string = textfield.text;
NSLog(#"%#", self.string);
}
if i remove everything except the NSLog in the selectfriends.m file in viewdidload method, the file wont upload to parse. and i get the alertView showing and error saying not file or directory exists.
then i upload to parse.com
- (void) uploadMessage;
{
NSString *text =string;
NSString *fileType= #"text";
NSString * fileName= #"text.txt";
NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding];
PFFile *file = [PFFile fileWithName:fileName data:data];
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Please try sending your message again" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
1) the string does not pass as null as null does not show in the console at runtime. (nothing shows in the console even with the NSLog statement)
2) the file uploads to parse.com as a text.txt file but when i download the file to check the text the file is blank.
So in quizVC type a question then press next to go to selectfirendsVC then select friends and upload to parse.com
how can i upload the textfile to show the user inputted text in the UITextView in the quizVC?
You don't want to communicate with just any instance of your SelectFriends vc, like the one created in your viewDidLoad. You must communicate with the instance that is going to be presented.
Remove the code in Quiz vc's viewDidLoad. Create a segue from Quiz vc to SelectFriends vc. In Quiz vc, implement prepareForSegue. In that method, interrogate the textView's text and set the string property on the segue.destinationViewController. That's the one that will be presented.
(A more general treatment of vc-vc communication here).
i want to get image from photo library in iPhone simulator by simply giving image name or full path. but i cant get idea to do that. please get me out from here.
Thank you in advance.
To access library directory you need to used NSLibraryDirectory instead of NSDocumentDirectory and then use following code. I think it might be useful to you.
NSString* fileName = [NSString stringWithFormat:#"%#.png",patientlastName];
NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];
UIImage *image1=[UIImage imageWithContentsOfFile:pdfFileName];
imageView.image=image1;
If you would like to open photo library and get image from there, you have to use UIImagePickerController and implements its delegate methods like below.
above the #implementation of your class in .m file, use folllowing code
#pragma mark - NON Rotation
#interface NonRotatingUIImagePickerController : UIImagePickerController
#end
#implementation NonRotatingUIImagePickerController
- (BOOL)shouldAutorotate
{
return NO;
}
#end
then in implementation of your class .m file use below code.
-(void)openPhotoLibrary {
if ([UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeSavedPhotosAlbum])
{
UIImagePickerController *imagePicker =
[[NonRotatingUIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType =
UIImagePickerControllerSourceTypePhotoLibrary;
imagePicker.mediaTypes = [NSArray arrayWithObjects:
(NSString *) kUTTypeImage,
nil];
imagePicker.allowsEditing = NO;
}
}
UIImagePickerController Delegates
#pragma mark - ImagePicker Controller Delegate
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info
objectForKey:UIImagePickerControllerMediaType];
[self dismissViewControllerAnimated:YES completion:nil];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage *image = [info
objectForKey:UIImagePickerControllerOriginalImage];
imageView.image = image;
}
}
-(void)image:(UIImage *)image
finishedSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo
{
if (error) {
//Right some error related code...
}
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self dismissViewControllerAnimated:YES completion:nil];
}
I think this would be helpful to you.
I've started a tiny project to get my head around NSViewControllers.
I have an AppController that handles a NSOpenPanel. Once I get a URL to a movie file, I pass it to a NSViewController subclass (NNMovieViewController). This is how I do it:
-(void)openMovieWithURL:(NSURL *)url {
NSError *error;
movie = [[QTMovie alloc] initWithURL:url error:&error];
[startButton setEnabled:YES];
[movieView setMovie:movie];
NSLog(#"button: %#", [startButton isEnabled]?#"YES":#"NO");
// logs "NO"
NSLog(#"movie: %#", movie);
// logs the correct movie object
NSLog(#"movieView: %#", [movieView movie]);
// logs "(null)"
}
The header file looks like this:
#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>
#interface NNMovieViewController : NSViewController {
QTMovie *movie;
BOOL playing;
IBOutlet QTMovieView *movieView;
IBOutlet NSButton *startButton;
}
-(IBAction)clickStart:(id)sender;
-(void)openMovieWithURL:(NSURL*)url;
#end
What am I missing? I re-did the whole thing in a project without a NSViewController and it just worked...
UPDATE
After I received the comments from Kreiri and Parag Bafna I tinkered a little bit more and found out that at the time I call [movieViewController openMovieWithURL:url]; inside my AppController the Outlets are not hooked up yet.
This is my AppController implementation:
#import "AppController.h"
#implementation AppController
#synthesize movieViewController;
- (void)awakeFromNib {
movieViewController = [[NNMovieViewController alloc] initWithNibName:#"NNMovieViewController" bundle:nil];
NSView *viewControllerView = [movieViewController view];
[view addSubview:viewControllerView];
}
- (IBAction)clickOpen:(id)sender {
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setCanChooseFiles:TRUE];
[dialog setCanChooseDirectories:FALSE];
[dialog setAllowsMultipleSelection:FALSE];
[dialog setAllowedFileTypes:[QTMovie movieFileTypes:0]];
if ([dialog runModal] == NSOKButton) {
NSURL *movieFileURL = [[dialog URLs] objectAtIndex:0];
[self openMovie:movieFileURL];
}
}
- (void)openMovie:(NSURL *)url {
NSLog(#"startButton: %#", [movieViewController movieView]);
// logs "null"
NSLog(#"startButton: %#", [movieViewController startButton]);
// logs "null"
NSLog(#"---------------------------------");
[movieViewController openMovieWithURL:url];
}
#end
Yes, silly me. In Interface Builder I hooked up my controls with the wrong object. I should have used File's Owner but instead I dragged in an NSObject and set its class to NNMovieViewController and connected the widgets to it.
I have an NSMutableArray of names. I want the pass the data (selected name) inside of NSMutableArray as text to another view's label.
FriendsController.m:
- (void)viewDidLoad {
[super viewDidLoad];
arrayOfNames=[[NSMutableArray alloc] init];
arrayOfIDs=[[NSMutableArray alloc] init];
userName=[[NSString alloc] init];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
long long fbid = [[arrayOfIDs objectAtIndex:indexPath.row]longLongValue];
NSString *user=[NSString stringWithFormat:#"%llu/picture",fbid];
[facebook requestWithGraphPath:user andDelegate:self];
userName=[NSString stringWithFormat:#"%#",[arrayOfNames objectAtIndex:indexPath.row]];
FriendDetail *profileDetailName = [[FriendDetail alloc] initWithNibName: #"FriendDetail" bundle: nil];
profileDetailName.nameString=userName;
[profileDetailName release];
}
- (void)request:(FBRequest *)request didLoad:(id)result {
if ([result isKindOfClass:[NSData class]]) {
transferImage = [[UIImage alloc] initWithData: result];
FriendDetail *profileDetailPicture = [[FriendDetail alloc] initWithNibName: #"FriendDetail" bundle: nil];
[profileDetailPicture view];
profileDetailPicture.profileImage.image= transferImage;
profileDetailPicture.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:profileDetailPicture animated:YES];
[profileDetailPicture release];
}
}
In FriendDetail.h
NSString nameString;
IBOutlet UILabel *profileName;
#property (nonatomic, retain) UILabel *profileName;
#property (nonatomic, retain) NSString *nameString;
In FriendDetail.m
- (void)viewDidLoad
{
[super viewDidLoad];
profileName.text=nameString;
}
nameString in second controller(FriendDetail) returns nil. When i set a breakpoint in firstcontroller I see the string inside of nameString is correct but after that it returns to nil somehow.
-----------------------EDIT----------------------------------------
According to answers I have improved my code little bit
FriendsController.h
FriendDetail *friendController;
#property (strong, nonatomic) FriendDetail *friendController;
FriendsController.m
- (void)viewDidLoad
{
[super viewDidLoad];
arrayOfNames=[[NSMutableArray alloc] init];
arrayOfIDs=[[NSMutableArray alloc] init];
arrayOfThumbnails=[[NSMutableArray alloc] init];
userName=[[NSString alloc] init];
friendController= [[FriendDetail alloc] initWithNibName: #"FriendDetail" bundle: nil];
}
-(void)request:(FBRequest *)request didLoad:(id)result{
if ([result isKindOfClass:[NSData class]])
{
transferImage = [[UIImage alloc] initWithData: result];
friendController.nameString=userName;
[friendController view];
friendController.profileImage.image= transferImage;
friendController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:friendController animated:YES];
}
//this is how i take facebook friends list
if ([result isKindOfClass:[NSDictionary class]]){
items = [[(NSDictionary *)result objectForKey:#"data"]retain];
for (int i=0; i<[items count]; i++) {
NSDictionary *friend = [items objectAtIndex:i];
long long fbid = [[friend objectForKey:#"id"]longLongValue];
NSString *name = [friend objectForKey:#"name"];
NSLog(#"id: %lld - Name: %#", fbid, name);
[arrayOfNames addObject:[NSString stringWithFormat:#"%#", name]];
[arrayOfIDs addObject:[NSNumber numberWithLongLong:fbid]];
}
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
long long fbid = [[arrayOfIDs objectAtIndex:indexPath.row]longLongValue];
NSString *user=[NSString stringWithFormat:#"%llu/picture",fbid];
userName=[NSString stringWithFormat:#"%#",[arrayOfNames objectAtIndex:indexPath.row]];
[facebook requestWithGraphPath:user andDelegate:self];
[username retain]
}
Now when i select row first time it sends name. When i come back to tableview and select another name it shows the old name.
If I delete [username retain] in didSelectRowAtIndexPath: it still sends nil to nameString
when I set break point at didSelectRowAtIndexPath: at line `
userName=[NSString stringWithFormat:#"%#",[arrayOfNames objectAtIndex:indexPath.row]]`
I can see userName = #"Adam Dart" which is correct
in my second breakpoint at line friendController.nameString=userName; I see that nameString =nil and userName = Variable is not CFString
ARC is set to NO
The value is nil because you did not pass the value in request:didLoad: function.
In function didSelectRowAtIndexPath, You create a local instance of another ViewController and set the value of nameString, but you did not present the view and release the ViewController immediately. You actually do nothing in these few lines of code:
FriendDetail *profileDetailName = [[FriendDetail alloc] initWithNibName: #"FriendDetail" bundle: nil];
profileDetailName.nameString = userName;
[profileDetailName release];
In function request:didLoad:, again you create a local instance of another ViewController with image. But this instance is only local to this function, which means no relation to the one created in didSelectRowAtIndexPath.
What you need to do is, remember the name of clicked row first in didSelectRowAtIndexPath, here you dont have to create the ViewController instance. When the request finish, set both the image and name to the controller and then present it. But you should avoid user from clicking different rows at the same time, because you don't know when the request finish.
You have two instances of FriendDetail called profileDetailPicture. Both of theses profileDetailPicture are not the same. So in your didSelectRowAtIndexPath method, the value that you assigned to the nameString will not be visible/available to the nameString of the profileDetailPicture In the request:(FBRequest *)request didLoad method.
Edit for solution:
Create an iVar or property (profileDetailPicture) in the FriendController.
Only do one allocation in the request:(...) method.
Remove the allocation statement in the didSelectRowAtIndexPath.
Any chance it has to do with the fact that you assign to profileDetailName and then immediately release it?
profileDetailName.nameString=userName;
[profileDetailName release];
You have to allocate the "first_controller" in your "second_controller"
to pass objects such as your string. and you would call the nameString differently.
example:
second_controller.h
#import "first_controller.h"
...
#interface second_controller : UIViewController{
first_controller* firstController;
}
second_controller.m
- (void)viewDidLoad {
[super viewDidLoad];
firstController = [[first_controller alloc] init];
profileName.text = firstController.nameString;
}
Which you'll have to init it correctly, because its two views sharing information.
I have view controllers A(FileListViewController) and B(TextFileViewController). A is a UITableViewController. What I am doing now is that after selecting a row in controller A, I load a text file and display that text in a UITextView in controller B.
The following is the header and implementation part(some code is abridged) of my the two controllers.
FileListViewcontroller Interface:
#interface FileListViewController : UITableViewController {
NSMutableArray * fileList;
DBRestClient* restClient;
TextFileViewController *tfvc;
}
#property (nonatomic, retain) NSMutableArray * fileList;
#property (nonatomic, retain) TextFileViewController *tfvc;
#end
FileListViewController Implementation:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DBMetadata *metaData = [fileList objectAtIndex:indexPath.row];
if(!metaData.isDirectory){
if([Utils isTextFile:metaData.path]){
if(!tfvc){
tfvc = [[TextFileViewController alloc] init];
}
[self restClient].delegate = self;
[[self restClient] loadFile:metaData.path intoPath:filePath];
[self.navigationController pushViewController:tfvc animated:YES];
}
}
- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath {
NSError *err = nil;
NSString *fileContent = [NSString stringWithContentsOfFile:destPath encoding:NSUTF8StringEncoding error:&err];
if(fileContent) {
[tfvc updateText:fileContent];
} else {
NSLog(#"Error reading %#: %#", destPath, err);
}
}
And here is the interface for TextFileViewController:
#interface TextFileViewController : UIViewController {
UITextView * textFileView;
}
#property (nonatomic, retain) IBOutlet UITextView * textFileView;
-(void) updateText:(NSString *) newString;
#end
TextFileViewController implementation:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done)] autorelease];
textFileView = [[UITextView alloc] init];
}
- (void) updateText:(NSString *)newString {
NSLog(#"new string has value? %#", newString);
[textFileView setText:[NSString stringWithString:newString]];
NSLog(#"print upddated text of textview: %#", textFileView.text);
[[self textFileView] setNeedsDisplay];
}
(void)restClient: loadedFile: will be call after the loadFile:intoPath: is completed in the disSelectRowAtIndexPath method.
In TextFileViewController's updateText method, from NSLog I see that the text property is updated correctly. But the screen does not update accordingly. I've tried setNeedsDisplay but in vain. Did I miss something?
Thanks in advance.
In -[TextFileViewController viewDidLoad] you're creating a UITextView, but its frame is never set, and it's not added to the view hierarchy.
Try changing this:
textFileView = [[UITextView alloc] init];
to this:
textFileView = [[UITextView alloc] initWithFrame:[[self view] bounds]];
[[self view] addSubview:textFileView];
The problem is that textFileView is created in the viewDidLoad method of TextFileViewController. This method has not yet been called by the time you call updateText (this happens before the TextFileViewController is pushed).
You can fix this by forcing the view to load before you call [[self restClient] loadFile:metaData.path intoPath:filePath];.