How to access view's subviews without crash while hooking Swift? (Jailbreak) - objective-c

I have an odd issue where when I change the background of a view, I can no longer see the UILabels that are subviews of that view. I cannot dump the headers for the Calculator app, as it is in Swift, and I don't know what the name of the UILabels are, and FLEXing is no help with that. I want to bring the subviews in front of the background view (there are two subviews), but both of my current methods crash the target app. I was trying to see if I could get it working with just one at the very front though.
My code:
NSMutableArray *labels = [NSMutableArray arrayWithArray:((DisplayView*)self).allSubviews];
if ([labels count] > 0) {
UILabel *mainLabel = labels[0];
[self bringSubviewToFront:mainLabel];
}
((DisplayView*)self).allSubviews = labels;
And I have also tried:
for (UILabel *label in ((DisplayView*)self).allSubviews) {
if ([label isKindOfClass:[UILabel class]]) {
[self bringSubviewToFront:label];
}
I need an alternative method of doing this, a way to bring all of the subviews forward, or someone to tell me what I am doing wrong.
Edit, here's my full code:
#interface DisplayView
#property (nonatomic, copy, readwrite) UIColor *backgroundColor;
#property (nonatomic, assign, readwrite, getter=isHidden) BOOL hidden;
#property (nonatomic, assign, readwrite, getter=isOpaque) BOOL opaque;
#property (nonatomic, assign, readwrite) CGFloat alpha;
#property (nonatomic, copy, readwrite) NSArray *allSubviews;
#property (nonatomic, assign, readwrite) CALayer *layer;
#end
%hook DisplayView
-(void)layoutSubviews {
((DisplayView*)self).opaque = NO;
((DisplayView*)self).backgroundColor = [UIColor blackColor];
((DisplayView*)self).hidden = NO;
((DisplayView*)self).alpha = 1.0;
NSMutableArray *labels = [NSMutableArray arrayWithArray:((DisplayView*)self).layer.sublayers];
if ([labels count] > 0) {
UILabel *mainLabel = labels[0];
[self bringSubviewToFront:mainLabel];
}
}
%end
%ctor {
%init(DisplayView=objc_getClass("Calculator.DisplayView"));
}

Seems like a few possible issues. First, there is no allSubviews on UIView, it probably should be subviews.
NSMutableArray *labels = [NSMutableArray arrayWithArray:((DisplayView*)self).subviews];
if ([labels count] > 0) {
UILabel *mainLabel = labels[0];
(Just want point out that you don't seem to change the array, so why copy it? You can directly access self.subviews)
Second, in the CALayer version, you're casting CALayer to UIView:
NSMutableArray *labels = [NSMutableArray arrayWithArray:((DisplayView*)self).layer.sublayers];
CALayer *mainLabel = labels.firstObject;
if (mainLabel) {
[labels removeObjectAtIndex:0];
[labels addObject:mainLabel];
self.layer.sublayers = labels;
}
Maybe some of this helps

Related

Which method is better / cleaner?

While looping the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {}
method im setting data to my custom cell (an image and a text). I was wondering what's the best way to achieve this.
I can use 2 different array (image array and text array) and do something like:
cell.image setImage: //get image from imageArray with indexPath.row
cell.text.text = [textArray objectAtIndex:indexPath.row];
or use a multi dimensional array like this:
cell.image setImage: [imageArray objectAtIndex:indexPath.row] objectAtIndex:0;
cell.text.text = [textArray objectAtIndex:indexPath.row] objectAtIndex:1;
// or use of dictionary with keys
What method is quicker or more readable?
Personally I think the following is the cleanest solution:
Create a model for the items in your array.
Create an UITableViewCell subclass to display the model in the cell. The subclass will have a property that accepts the model and redraw itself when the model changes.
Let's say we have a news app. The array is filled with items for which we create the model NewsItem. The model header could look like this:
NewsItem.h
#interface FSNewsItem : NSObject <NSCoding>
#property (nonatomic, copy, readonly) NSString *title;
#property (nonatomic, copy, readonly) NSURL *URL;
#property (nonatomic, copy, readonly) NSString *time;
#property (nonatomic, copy, readonly) NSString *points;
#property (nonatomic, copy, readonly) NSString *author;
// initialiser
+ (FSNewsItem *)newsItemWithTitleNode:(HTMLNode *)node
subTextNode:(HTMLNode *)subNode;
#end
Now for the cell we create a NewsItemCell. The code for NewsItemCell might look like the following:
NewsItemCell.h
#interface FSNewsItemCell : UITableViewCell
#property (nonatomic, strong) FSNewsItem *newsItem;
#end
NewsItemCell.m
#interface FSNewsCell ()
#property (nonatomic, strong) UILabel *titleLabel;
#property (nonatomic, strong) UILabel *detailLabel;
#end
#implementation FSNewsItemCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
self.titleLabel = [[UILabel alloc] init];
[self addSubview:_titleLabel];
self.detailLabel = [[UILabel alloc] init];
[self addSubview:_detailLabel];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
const CGRect bounds = self.bounds;
CGFloat width = bounds.size.width - (FS_FLOAT_PADDING * 2) - 15.0f;
_titleLabel.frame = CGRectMake(FS_FLOAT_PADDING, FS_FLOAT_CELL_PADDING_VERTICAL, width, 20.0f);
CGFloat y = _titleLabel.frame.size.height + (FS_FLOAT_CELL_PADDING_VERTICAL * 2);
_detailLabel.frame = CGRectMake(FS_FLOAT_PADDING, y, width, 15.0f);
}
#pragma mark - Private
- (void)setNewsItem:(FSNewsItem *)newsItem
{
if (_newsItem == newsItem)
{
return;
}
_newsItem = newsItem;
self.titleLabel.text = newsItem.title;
if (_newsItem.points && _newsItem.time)
{
self.detailLabel.text = [NSString stringWithFormat:#"%# | %#", _newsItem.points, newsItem.time];
}
else
{
self.detailLabel.text = newsItem.time;
}
[self setNeedsLayout];
}
Finally, when we want to display the news item in the cell, our code would look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
FSNewsItemCell *cell = (FSNewsItemCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[FSNewsItemCell alloc] initWithStyle:UITableViewCellStylePlain reuseIdentifier:CellIdentifier];
}
cell.newsItem = [_items objectAtIndex:indexPath.row];
return cell;
}
This is in my opinion the cleanest solution and the approach I take most of the time. I like to keep my view controllers small. I also like the fact that my view controller doesn't have to know which controls my (custom) table view cell has. The table view cell gets full responsibility on how to draw itself depending on the data supplied.
I would add a simple data class and put class instances in the NSArray. Or use a NSArray of NSDictionary objects so the items can be addressed by name, not position.
Example class code (classes are very easy these days):
#interface DisplayItems : NSObject
#property (nonatomic, strong) NSString *text;
#property (nonatomic, strong) UIImage *image;
#end
#implementation DisplayItems
#end
One idea is to create a custom class:
#interface CellInfo : NSObject
#property (....) UIImage *image;
#property (....) NSString *text;
#end
Then:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
CellInfo *info = [self.cellInfoArray objectAtIndex:indexPath.row];
cell.image = info.image;
cell.text = info.text;
...
}

How to save UIImages in view

In my app I have two views: a mapView, and a detail view. The mapView has annotations, and the annotation views have right callout buttons that push the detail view onto the stack when the callout button is pressed. In the detail view, the user can take a picture that appears on the UIImage that is on the detail view controller. What I want is for a different image to be on the detail view controller for each annotation. I have read that I can use a singleton, and I have made a singleton class, but I do not know how to use it. Can someone help me out?
Here is some code:
My MKAnnotation class:
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#interface MapPoint : NSObject <MKAnnotation>
{
NSString *_address;
CLLocationCoordinate2D _coordinate;
NSNumber *_identifier;
UIImage *_image;
}
- (id)initWithAddress:(NSString*)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t
identifier:(NSNumber *)ident;
//This is a required property from MKAnnotation
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
//This is an optional property from MKAnnotataion
#property (nonatomic, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
#property (nonatomic) BOOL animatesDrop;
#property (nonatomic) BOOL canShowCallout;
#property (copy) NSString *address;
#property (copy) NSNumber *identifier;
#property (copy,nonatomic) UIImage *image;
#end
#implementation MapPoint
#synthesize title, subtitle, animatesDrop, canShowCallout;
#synthesize address = _address, coordinate = _coordinate, identifier = _identifier, image = _image;
-(id)initWithAddress:(NSString *)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t
identifier:(NSNumber *)ident
{
self = [super init];
if (self) {
_address = [address copy];
_coordinate = coordinate;
_identifier = ident;
[self setTitle:t];
NSDate *theDate = [NSDate date];
subtitle = [NSDateFormatter localizedStringFromDate:theDate
dateStyle:NSDateFormatterMediumStyle
timeStyle:NSDateFormatterMediumStyle];
}
return self;
}
#end
Here is the detail view controller:
#import <UIKit/UIKit.h>
#class P2OViewController;
#interface PinViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate,UITextFieldDelegate>
{
__weak IBOutlet UILabel *label;
__weak IBOutlet UIButton *removeButton;
NSString *addressForLabel;
__weak P2OViewController *_vc;
__weak NSNumber *_identifier;
}
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#property (weak, nonatomic) IBOutlet UILabel *label;
#property (weak, nonatomic) IBOutlet UIButton *removeButton;
#property (weak, nonatomic) P2OViewController *vc;
#property (weak, nonatomic) NSNumber *identifier;
-(void)takePicture:(id)sender;
-(void)buttonPressed:(id)sender;
#end
#import "PinViewController.h"
#import "MapPoint.h"
#import "P2OViewController.h"
#implementation PinViewController
#synthesize imageView, label, removeButton;
#synthesize vc = _vc, identifier = _identifier;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
UIBarButtonItem *pic = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
target:self
action:#selector(takePicture:)];
[[self navigationItem]setRightBarButtonItem:pic];
}
return self;
}
-(void)takePicture:(id)sender
{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init];
//If our device has a camera, we ant to take a picture, otherwise, we just pick from photo library
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
} else {
[imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
}
[imagePicker setDelegate:self];
//Place the image picker on the screen
[self presentViewController:imagePicker
animated:YES
completion:nil];
}
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
//Get picked image from info dictionary
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
//Put the image onto the screen in out image view
[imageView setImage:image];
//Take image picker off the screen - you must call this dismiss method
[self dismissViewControllerAnimated:YES
completion:nil];
}
#end
You surely have a class implementing the MKAnnotation protocol.
In this class simply add a UIImage property and assign to every annotation its custom image.
Then when presenting the detailed view you just pass around the annotation and access its image property you defined.
EDIT
In your PinViewController simply declare a property like this
#property(nonatomic, strong) MapPoint * annotation;
Before you present your PinViewController assign the corresponding annotation to it, doing something like
detailedController.annotation = theAnnotation;
Do whatever you need to do to get the image, then store it in the annotation in this way
self.annotation.image = theImage;
Now the annotation is storing the image, so as long as you keep it in memory it will be there available for you.
The next time you push the detailed view you can perform a check to see whether that annotation has already an image or not
if(nil != self.annotation.image) {
// do something
} else {
// do something else
}

Filled NSArray unintentionally loses its content

An array is filled properly but when I try to access to content again, seems that is empty again!! trying to post as many code as necessary. Thanks for help.
I declare appData in .h file:
#interface userViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
NSArray *appData;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UILabel *status;
IBOutlet UITableView *mainTable;
}
#property (nonatomic, retain) NSArray *appData;
#property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
#property (nonatomic, retain) UILabel *status;
#property (nonatomic, retain) UITableView *mainTable;
- (IBAction) refresca: (id)sender;
- (void)getData: (NSString *)usuari: (NSString *)clau;
#end
in .m file is synthesized and released. appData is properly filled when connection request ends and then, when I reload tableview, when numberOfRowsInSection executed (while loop also in order to test), appData is empty!
- (void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"%#", [request responseString]);
self.appData = [[request responseString] componentsSeparatedByString: #"#"];
int x =0;
while (x<[appData count] - 1)
{
NSLog(#"Aplicaciones = %#",[appData objectAtIndex: x]);
x = x+1;
}
[activityIndicator stopAnimating];
activityIndicator.hidden = YES;
status.hidden = YES;
mainTable.hidden = NO;
[mainTable reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([appData count] != 0)
{
NSLog(#"Counter = %#",[appData objectAtIndex:0]);
}
return [appData count]-1;
}
A couple of things here.
Your test logging in numberOfRows will cause the app to hang if appdata ever has a non-zero count.
Are you sure you are getting the the same appData object that you populate in requestFinished ?
I suggest using the accessor in numberOfRows as in [self.appData count] which might sort out the problem.
And is there a specific reason you subtract one from the count? As you will lose one element from the array in the tableView that way

Popover with no size

I made a popover class that I could call easily for basic popovers - you give it data and set the size and it should do the rest.
This was working fine until iOS5, now the popover opens but with just the border, no white space or content at all.
I've searched and searched, any ideas you could throw my way would be great.
#protocol BasicPickerDelegate
- (void)basicPickerItemSelected:(NSMutableDictionary *)thisDic;
#end
#interface BasicPickerController : UITableViewController {
// data
IBOutlet NSMutableArray *theData;
// stuff to set
IBOutlet int myWidth;
IBOutlet int myHeight;
IBOutlet int myPopoverNum;
// delegate
id<BasicPickerDelegate> delegate;
}
#property (nonatomic, retain) IBOutlet NSMutableArray *theData;
#property (nonatomic, assign) IBOutlet int myWidth;
#property (nonatomic, assign) IBOutlet int myHeight;
#property (nonatomic, assign) IBOutlet int myPopoverNum;
#property (nonatomic, assign) id<BasicPickerDelegate> delegate;
- (void)setSize;
- (void)checkData;
#end
Then the setSize function is viewDidLoad
- (void)setSize {
if (!myWidth || !myHeight) {
NSLog(#"WIDTH AND HEIGHT NOT SET, SETTING DEFAULTS");
myWidth = 100;
myHeight = 100;
}
self.contentSizeForViewInPopover = CGSizeMake(myWidth, myHeight);
}
Then I call it like this:
- (IBAction)showBasicPopover:(id)sender {
// Show Basic Popover - can be reused
if (basicPicker != nil) {
self.basicPicker = nil;
[self.basicPicker release];
}
self.basicPicker = [[[BasicPickerController alloc] initWithStyle:UITableViewStylePlain] autorelease];
basicPicker.delegate = self;
self.basicPickerPopover = [[[UIPopoverController alloc]
initWithContentViewController:basicPicker] autorelease];
// Give popover the data it needs
NSMutableDictionary *sizeDic = [self returnPopoverSize:[sender tag]];
self.basicPicker.myHeight = [[sizeDic objectForKey:#"height"] intValue];
self.basicPicker.myWidth = [[sizeDic objectForKey:#"width"] intValue];
self.basicPicker.myPopoverNum = [sender tag];
[basicPicker viewDidLoad];
self.basicPicker.theData = [self returnPopoverData:[sender tag]];
NSLog(#"giving popover dic (%d) with %d derps", [sender tag], [self.basicPicker.theData count]);
// Set display settings and show popover
[basicPicker viewWillAppear:YES];
CGRect popoverRect = [self.view convertRect:[sender frame]
fromView:[sender superview]];
[self.basicPickerPopover presentPopoverFromRect:popoverRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
When I run it, the WIDTH AND HEIGHT NOT SET, SETTIN DEFAULTS dialogue comes up. For some reason it's not reading in the values it's given. Though with some fiddling, even if I can get it to read them in it don't think they are valid and overrides them.
edit: So basically:
With setSize being called in viewDidLoad it doesn't know what the width and height is. So it sets the default.
If setSize isn't called in viewDidLoad, it comes up with "no size" - ie it has popover border but no content in it at all.
When setSize is called in viewWillAppear, viewDidAppear or anything like that (after viewDidLoad) it doesn't actually set the size of the popover.
Finally figured this out.
I needed to assign the width/height variables after creating the popover but before initialising it.
Revised code below:
self.basicPicker = [[[BasicPickerController alloc] initWithStyle:UITableViewStylePlain] autorelease];
// Give popover the data it needs
basicPicker.delegate = self;
NSMutableDictionary *sizeDic = [self returnPopoverSize:[sender tag]];
self.basicPicker.myHeight = [[sizeDic objectForKey:#"height"] intValue];
self.basicPicker.myWidth = [[sizeDic objectForKey:#"width"] intValue];
self.basicPicker.myPopoverNum = [sender tag];
// Now init
self.basicPickerPopover = [[[UIPopoverController alloc]
initWithContentViewController:basicPicker] autorelease];
[basicPicker viewDidLoad];

Gaining access to an NSMutableArray

I am new to arrays and objects. I have a class HowToPlay.h (of course that has a .m also) their i define two arrays
NSMutableArray *nibs;
NSMutableArray *unusedNibs;
#property (nonatomic, retain) NSMutableArray *nibs;
#property (nonatomic, retain) NSMutableArray *unusedNibs;
then we jump into the .m,
i write all the stuff for the arrays,
nibs = [[NSMutableArray alloc]initWithObjects:#"Question 2", #"Question 3", nil];
self.unusedNibs = nibs;
[nibs release];
Then i also have another class called Question 1, i need to be able to use this array in that class, and be able to change it, but keep the changes on the HowToPlay.m file.
here is why, basically this array loads random NIB files, and then deletes them from the array when they have been used.
in Question 1.m here is what im doing to use the array
random = arc4random() % [self.unusedNibs count];
NSString *nibName = [self.unusedNibs objectAtIndex:random];
[self.unusedNibs removeObjectAtIndex:random];
if (nibName == #"Question 3") {
Question_3 *Q3 = [[Question_3 alloc] initWithNibName:#"Question 3" bundle:nil];
Q3.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:Q3 animated:YES];
[Q3 release];
}
if (nibName == #"Question 2") {
Question_2 *Q2 = [[Question_2 alloc] initWithNibName:#"Question 2" bundle:nil];
Q2.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:Q2 animated:YES];
[Q2 release];
}
This all seems to work fine, but the problem im having is it seems that objects in the array are not getting deleted, even though this line runs
[self.unusedNibs removeObjectAtIndex:random];
I haved tried making it
[HowToPlay.unusedNibs removeObjectAtIndex:random];
I get a error saying expected '.' before '.' token
It seems to me that i have access to read the array, but not to change it. any way to fix this so i can change the array? thanks
UPDATE:
here is the whole HowToPlay.h file contents:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
int score;
int usedQ1;
int usedQ2;
int usedQ3;
NSMutableArray *nibs;
NSMutableArray *unusedNibs;
#interface HowToPlay : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
UIPickerView *selectType;
NSMutableArray *selectArray;
AVAudioPlayer *audioPlayer;
UIActivityIndicatorView *progress;
UIButton *worldButton;
UIButton *politicsButton;
UIButton *starButton;
}
#property (nonatomic, retain) NSMutableArray *nibs;
#property (nonatomic, retain) NSMutableArray *unusedNibs;
#property (nonatomic, retain) IBOutlet UIButton *worldButton;
#property (nonatomic, retain) IBOutlet UIButton *politicsButton;
#property (nonatomic, retain) IBOutlet UIButton *starButton;
#property (nonatomic, retain) IBOutlet UIPickerView *selectType;
#property (nonatomic) int usedQ1;
#property (nonatomic) int usedQ2;
#property (nonatomic) int usedQ3;
#property (readwrite) int score;
-(IBAction)World:(id)sender;
- (IBAction)Politics:(id)sender;
-(IBAction)Stars:(id)sender;
#end
#import "MainViewController.h"
#import "Question 1.h"
#import "Question 2.h"
#import "Question 3.h"
I import after because otherwise i get errors
Also I have the array set up on theViewDidLoad part of HowToPlay, is this a bad idea?
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
NSURL *click = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/click.wav", [[NSBundle mainBundle] resourcePath]]];
audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:click error:nil];
audioPlayer.numberOfLoops = 1;
progress = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
progress.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
progress.center = self.view.center;
[self.view addSubview: progress];
usedQ1 = 0;
usedQ2 = 0;
usedQ3 = 0;
selectArray = [[NSMutableArray alloc]init];
[selectArray addObject:#"World"];
[selectArray addObject:#"Politics"];
[selectArray addObject:#"Stars"];
score = 0;
nibs = [[NSMutableArray alloc]initWithObjects:#"Question 2", #"Question 3", nil];
self.unusedNibs = nibs;
[nibs release];
}
How do you check if objects are actually deleted? Please post some more code as the way you remove objects from unusedNibs seems to be fine. As per this code:
[self.unusedNibs removeObjectAtIndex:random];
and this code
[HowToPlay.unusedNibs removeObjectAtIndex:random];
The reason why you're getting this error is because you're trying to access unusedNibs using a class name "HowToPlay" instead of its instance "self".
A couple of things: first, to call properties like unusedNibs in HowToPlay it looks like you are calling the class and not an instance. You need to create a HowToPlay object and assign it to a property in your Question1 object so the question one Object has something to call to. Question1 should not call 'self' for unusedNibs since it does not own it.
With what you are doing it might make more sense to put the function figuring out the next random controller in the HowToPlay class. That way your question view controllers could just ask it to return which controller is next without having to duplicate that code across every question controller.