After setting UITextView's text property, screen does not update accordingly - objective-c

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];.

Related

Custom view for NSPopupButton NSMenu

I'm having some problems setting a custom NSView for my NSPopupButton menu items. Here is what I've got so far:
#interface ViewController ()
#property (weak) IBOutlet NSPopUpButton *popupButton;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
for(int i = 0; i < 25; i++) {
NSMenuItem *menuItem = [[NSMenuItem alloc ] initWithTitle:[NSString stringWithFormat:#"%d", i] action:#selector(itemClicked:) keyEquivalent:#""];
MenuView *menuView = [[MenuView alloc] initWithFrame:CGRectMake(0, 0, 184, 50)];
menuView.displayText.stringValue = #"This is a test";
[menuItem setView:menuView];
[self.popupButton.menu addItem:menuItem];
}
}
- (void)itemClicked:(id)sender {
}
#end
//My custom view
#implementation MenuView
- (id)initWithFrame:(NSRect)frameRect {
NSString* nibName = NSStringFromClass([self class]);
self = [super initWithFrame:frameRect];
if (self) {
if ([[NSBundle mainBundle] loadNibNamed:nibName
owner:self
topLevelObjects:nil]) {
[self configureView];
}
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self configureView];
}
- (void)configureView {
[self setWantsLayer:YES];
self.layer.backgroundColor = [NSColor blueColor].CGColor;
}
#end
//Here is what my xib MenuView looks like
And here is the problem:
This seems like it should be a fairly straight forward task but I'm not sure what is happening to my view and why my label on the view seems to disappear and is not showing any text for each of the views. I was poking around in the documentation and stumbled across this for the NSPopupButton Menu :
// Overrides behavior of NSView. This is the menu for the popup, not a context menu. PopUpButtons do not have context menus.
#property (nullable, strong) NSMenu *menu;
I'm not sure if there is something that I'm doing wrong that is causing this problem or if what I'm trying to do in this context is not achievable off of an NSPopupButton NSMenu. If anyone has any experience with this and could offer advice I'd really appreciate it.

The first method in detail view controller is being skipped

Is there something important I need in RoomItem to ensure that this method isn't skipped? It's the very first one in my detail view controller, and it is continually skipped when I'm in debugging mode. I'm sure I'm missing something ridiculously simple, but I've been staring at it for hours and just can't figure out what it is.
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
- (void)setDetailItem:(RoomItem *)newDetailItem
{
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
[_roomTxt setText:[_detailItem room]];
[_buildingTxt setText:[_detailItem building]];
[_dateTxt setText:[self dateCreatedString]];
[_buildingImageView setImage:[_detailItem buildingImage]];
_oi = [_detailItem objectIndex];
}
}
MasterViewController (root table view) methods that alloc and init new and existing detailViewControllers
- (void)insertNewObject:(id)sender
{
//add button invokes this
DetailViewController *ivc = [[DetailViewController alloc] init];
[self.navigationController pushViewController:ivc animated:YES];
NSLog(#"detailViewController allocated and initialized: %#", ivc);
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
NSLog(#"detailViewController initialized: %#", self.detailViewController);
}
//navigates to detailViewController and passes it the item's data
self.detailViewController.detailItem = [[[RoomList sharedStore] getAllItems] objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
Here is the tableView:didSelectRowAtIndexPath method that should be passing everything needed by the detail view controller, from the RoomList:sharedStore:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
//navigates to detailViewController and passes it the item's data
self.detailViewController.detailItem = [[[RoomList sharedStore] getAllItems] objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
...and the RoomItem.m file:
- (void)awakeFromFetch
{
[super awakeFromFetch];
UIImage *pic = [UIImage imageWithData:[self buildingThumbnailData]];
[self setPrimitiveValue:pic forKey:#"buildingThumbnail"];
}
- (id)initWithRoom:(NSString *)room Building:(NSString *)building
{
self = [super init];
if (self) {
[self setRoom:room];
[self setBuilding:building];
}
return self;
}
DetailViewController.h
#import <UIKit/UIKit.h>
#class RoomItem;
//pic edit: added delegates
#interface DetailViewController : UIViewController <UINavigationControllerDelegate,
UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate,
UIPageViewControllerDelegate>
{
__weak IBOutlet UITextField *roomField;
__weak IBOutlet UITextField *buildingField;
__weak IBOutlet UILabel *dateLabel;
UIPopoverController *imagePickerPopover;
}
#property (nonatomic, strong) RoomItem *detailItem;
#property (weak, nonatomic) IBOutlet UIButton *updateBtn;
#property (weak, nonatomic) IBOutlet UIButton *detailsBtn;
#property (weak, nonatomic) IBOutlet UITextField *roomTxt;
#property (weak, nonatomic) IBOutlet UITextField *buildingTxt;
#property (weak, nonatomic) IBOutlet UILabel *dateTxt;
#property (weak, nonatomic) IBOutlet UIImageView *buildingImageView;
#property (weak, nonatomic) UIImage *buildingImage;
#property (weak, nonatomic) NSNumber *oi;
- (IBAction)backgroundTapped:(id)sender;
- (IBAction)takePicture:(id)sender;
- (IBAction)updateRoomItem:(id)sender;
- (IBAction)goToReportDetails:(id)sender;
#end
Edit:here is a pseudo-UML diagram that illustrates what I'm seeing when I step through with the debugger (it reads from left to right, top to bottom):
It is very strange that the setter is not being called. I would take the line
self.detailViewController.detailItem = [[[RoomList sharedStore] getAllItems] objectAtIndex:[indexPath row]];
and replace it with this code which might be a bit easier to follow:
RoomItem *tempItem = [[[RoomList sharedStore] getAllItems] objectAtIndex:[indexPath row]];
DetailViewController *tempDVC = self.detailViewController; // could just use _detailViewController
tempDVC.detailItem = tempItem; // breakpoint on this line
and then when the breakpoint hits, I would single step through the compiled assembler to find out where it goes. There are instructions for that at Xcode Debugger - how to single step at level of CPU instructions - specifically the answer a couple of weeks ago from Max MacLeod.
I'd have written this as another comment since it's not really an answer, but it's too long!
Alrighty, first things first. The reason -(void)setDetailItem:(RoomItem *)newDetailItem was being skipped is due to me not passing it the newly created RoomItem in the DVC. I was doing this initially, but mistakenly got rid of it while trying to correct the issue somewhere else.
In DetailViewController.m:
- (void)insertNewObject:(id)sender
{
RoomItem *newItem = [[RoomList sharedStore] createItem];
DetailViewController *ivc = [[DetailViewController alloc] init];
[ivc setDetailItem:newItem];
[self.navigationController pushViewController:ivc animated:YES];
NSLog(#"detailViewController allocated and initialized: %#", ivc);
}
Getting this to work though took some restructuring in the RoomList.m file. I had initially attempted to marry the create and update functions into one method. That doesn't fly if I'm creating in the MVC, and updating in the DVC.
So I split the two up:
- (RoomItem *)createItem
{
double order;
//create new roomItem
//tracks what number item it's creating
if ([allItems count] == 0) {
order = 1.0;
}
else
{
order = [[[allItems lastObject] objectIndex] doubleValue] + 1;
}
NSLog(#"Adding after %d items, order = %.2f", [allItems count], order);
RoomItem *p = [NSEntityDescription insertNewObjectForEntityForName:#"RoomItem"
inManagedObjectContext:context];
[p setObjectIndex:[NSNumber numberWithDouble:order]];
[p setDateCreated:[NSDate date]];
[allItems addObject:p];
[self saveChanges];
return p;
}
- (RoomItem *)updateItemWithRoom:(NSString *)room Building:(NSString *)building ObjectIndex:(NSNumber *)objectIndex DateCreated:(NSDate *)dateCreated ImageKey:(NSString *)imageKey BuildingImage:(UIImage *)buildingImage BuildingThumbnail:(UIImage *)buildingThumbnail BuildingThumbnailData:(NSData *)buildingThumbnailData
{
NSError *error = nil;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"RoomItem" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
int32_t oid = [objectIndex integerValue] - 1;
NSLog(#"objectIndex = %d", oid);
RoomItem *currentRoomItem = [[context executeFetchRequest:request error:&error] objectAtIndex:oid];
//[currentRoomItem setItemEdited:YES];
[currentRoomItem setRoom:room];
[currentRoomItem setBuilding:building];
[currentRoomItem setImageKey:imageKey];
[currentRoomItem setBuildingImage:buildingImage];
[currentRoomItem setBuildingThumbnail:buildingThumbnail];
[currentRoomItem setBuildingThumbnailData:buildingThumbnailData];
[self saveChanges];
return currentRoomItem;
}
Lastly, the setDetailItem and configureView methods:
- (void)setDetailItem:(RoomItem *)detailItem
{
if (_detailItem != detailItem) {
_detailItem = detailItem;
[self configureView];
}
}
- (void)configureView
{
if (self.detailItem) {
[_roomTxt setText:[_detailItem room]];
[_buildingTxt setText:[_detailItem building]];
[_dateTxt setText:[self dateCreatedString]];
[_buildingImageView setImage:[_detailItem buildingImage]];
}
}
This should serve as a lesson about taking the time to read the core data documentation, or any other for that matter...especially noobs like myself.
Thanks again to emrys57 for the help.

MKAnnotationView Customize callout - handle touch on a button

I'm using the J4n0 Callout code (github), to implement a custom annotation in MapKit.
In my annotation (MyCalloutView) I'm using a button and a label.
When I'm clicking on my button, the methode handleTouch is called, but the sender correspond an UITapGestureRecognizer with sender.view always equal to my annotation view, and not the button.
MyCalloutView.h
#interface MyCalloutView : CalloutView
#property (nonatomic, retain) IBOutlet UILabel* title;
#property (weak, nonatomic) IBOutlet UIButton *clickButton;
- (IBAction) handleTouch:(id)sender;
- (id) initWithAnnotation:(CalloutAnnotation*)annotation;
- (IBAction)onClickButton:(id)sender;
#end
MyCalloutView.m
#implementation MyCalloutView
-(IBAction) handleTouch:(UITapGestureRecognizer *)sender {
//LogDebug(#"touch from : %#", sender);
UIButton *senderButton = (UIButton *)sender.view;
LogDebug(#"Sender class : %# - Sender Tag : %d - Sender View class : %#", [sender class], sender.view.tag, sender.view.class);
LogDebug(#"Tap postion : (%f, %f)", [sender locationInView:sender.view].x, [sender locationInView:sender.view].y);
if(senderButton == self.clickButton){
LogDebug(#"le clique vient de click button !!");
}
}
[...]
CalloutView.h
#class CalloutAnnotation;
#interface CalloutView : BaseCalloutView
- (IBAction) handleTouch:(id)sender;
- (id)initWithAnnotation:(CalloutAnnotation*)annotation;
#end
CalloutView.m
#implementation CalloutView
-(IBAction) handleTouch:(id)sender {
LogDebug(#"touch %#", sender);
}
- (id)initWithAnnotation:(CalloutAnnotation*)annotation
{
NSString *identifier = NSStringFromClass([self class]);
self = [super initWithAnnotation:annotation reuseIdentifier:identifier];
if (self!=nil){
[[NSBundle mainBundle] loadNibNamed:identifier owner:self options:nil];
}
// prevent the tap and double tap from reaching views underneath
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouch:)];
[self addGestureRecognizer:tapGestureRecognizer];
UITapGestureRecognizer *doubletapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouch:)];
doubletapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubletapGestureRecognizer];
return self;
}
#end
This issue was a well known one. The solution is described in the GitHub : issue 1
The solution works for me.

NSMutableArray to NSString and Passing NSString to Another View IOS5.1

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.

iOS: get image label

I have this code:
- (IBAction) checkIt:(id)sender{
NSString *button = [[(UIImageView *)sender label]text];
I want to get the label of the UIImageView. What am I doing wrong? This works with buttons...Not sure I understand how to access attributes correctly.
Subclass UIImageView. Add a string member and property for accessing it. Instead of creating an object of UIImageView, create the object of your class.
In .h
#interface CustomImageView : UIImageView
{
NSString *imageName;
}
#property (nonatomic, retain) NSString *imageName;
#end
in .m
#implementation CustomImageView
#synthesize imageName;
-(void) dealloc
{
[imageName release];
[super dealloc];
}
#end
in the file where you want to use this class
-(void) addCustomImageView
{
CustomImageView *imgView = [[CustomImageView alloc]initWithImage:[UIImage imageNamed:#"abc.png"]];
imgView.frame = CGRectMake(0, 0, 100, 100);
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(imgViewtapped:)];
[imgView addGestureRecognizer:tap];
[tap release];
[self.view addSubview:imgView];
[imgView release];
}
-(NSString *) imgViewtapped:(CustomImageView *)sender
{
return [sender imageName];
}
UIImageViews do not have labels.
https://developer.apple.com/library/ios/#documentation/uikit/reference/UIImageView_Class/Reference/Reference.html