Custom Annotation, Custom Callout with Buttons, with actions - ios

This is my first time dealing with annotations and callouts. I've got it working for the most part ( albeit, maybe not the correct way )
So, I have my mapView:
#interface MapViewController : BaseUIViewController
<MKMapViewDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *mapView;
#property (strong, nonatomic) NSMutableArray *friendsAnnotations;
#property (strong, nonatomic) NSMutableArray *friendsResults;
#property (strong, nonatomic) NSTimer *reloadTimer;
- (IBAction)zoomButtonPressed:(id)sender;
#property (strong, nonatomic) IBOutlet UIButton *zoomButton;
#end
The timer, I have set to reload the map every minute while you are on that screen. It calls these functions:
#pragma mark - Update Map Annotations
- (void) getFriendsForAnnotations {
[User syncUserWithCompletionHandler:^{
[ServerComm listCollection:UsersCollection withValues:[DEFAULTS objectForKey:userActiveFriendsKey] forKey:mongoIDKey withCompletionHandler:^(NSDictionary *response) {
[self.mapView removeAnnotations:self.friendsAnnotations];
[self.friendsAnnotations removeAllObjects];
self.friendsResults = [[response objectForKey:#"results"] mutableCopy];
dispatch_group_t group = dispatch_group_create();
for (id userDic in self.friendsResults) {
User *theUser = [User userFromDictionary:userDic];
dispatch_group_enter(group);
if ([[userDic objectForKey:userPrivacyLevelKey] integerValue] == 0 ||
[[userDic objectForKey:userPrivacyLevelKey] integerValue] == 1
){
NSDictionary *locationDic = [userDic objectForKey: userLastLocationKey];
BOOL checkedIn = FALSE;
if(![theUser.checkedInLocationName isEqualToString:EMPTYSTRING]) {
checkedIn = TRUE;
}
CustomAnnotation *CA = [[CustomAnnotation alloc] initWithTitle:[userDic objectForKey:userUsernameKey] Location:CLLocationCoordinate2DMake([[locationDic objectForKey:#"latitude"] doubleValue], [[locationDic objectForKey:#"longitude"] doubleValue]) UserImage:[userDic objectForKey:userProfilePictureURLKey] isCheckedIn:checkedIn];
[CA setCalloutUser:theUser];
[self.friendsAnnotations addObject: CA];
}
dispatch_group_leave(group);
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self plotAnnotationsOnMap:self.friendsAnnotations];
});
}];
}];
}
- (void) plotAnnotationsOnMap: (NSArray *) arrayOfLocations {
[self.mapView addAnnotations: self.friendsAnnotations];
}
The point to this, is to setup the initial annotation for use with the standard initWithAnnotation method.
The final step to plotting our annotations on the map is the viewForAnnotation method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[CustomAnnotation class]]) {
CustomAnnotation *myAnnotation = ((CustomAnnotation *)annotation);
CustomAnnotation *annotationView = (CustomAnnotation *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"customAnnotation"];
if (annotationView == nil) {
annotationView = [[CustomAnnotation alloc] initWithAnnotation:myAnnotation.annotation reuseIdentifier:#"customAnnotation"];
}
[annotationView setAnnotation:annotation];
[annotationView.userImage sd_setImageWithURL:[NSURL URLWithString:myAnnotation.userImageString]];
[annotationView setUserImageString:myAnnotation.userImageString];
[annotationView setCalloutUser:myAnnotation.calloutUser];
if (myAnnotation.isCheckedIn) {
annotationView.markerImage.image = [annotationView.markerImage.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[annotationView.markerImage setTintColor:COLOR_BLUE_MUTED];
} else {
annotationView.markerImage.image = [annotationView.markerImage.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[annotationView.markerImage setTintColor:COLOR_GREY_LIGHT];
}
annotationView.centerOffset = CGPointMake(0, -39);
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType: UIButtonTypeDetailDisclosure];
annotationView.canShowCallout = NO;
return annotationView;
} else {
return nil;
}
}
This annotation is defined with a CustomAnnotation.h/m file as you would normally expect, and is pulled in from a xib.
#import <Foundation/Foundation.h>
#import "CustomAnnotationCallout.h"
#import <MapKit/MapKit.h>
#interface CustomAnnotation : MKAnnotationView <MKAnnotation>
#property (nonatomic, assign) IBOutlet UIView* loadedView;
#property (nonatomic, strong) User *calloutUser;
#property (nonatomic, strong) UIView *calloutView;
#property (retain, nonatomic) IBOutlet UIImageView *markerImage;
#property (retain, nonatomic) IBOutlet UIImageView *userImage;
#property (retain, nonatomic) NSString *userImageString;
#property (copy, nonatomic) NSString *title;
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (assign, nonatomic) BOOL isCheckedIn;
#property (assign, nonatomic) BOOL showCustomCallout;
#property (assign, nonatomic) CGRect endFrame;
- (id) initWithTitle:(NSString *)newTitle Location:(CLLocationCoordinate2D)location UserImage:(NSString *)userImageString isCheckedIn:(BOOL)checkedIn;
- (void) setShowCustomCallout:(BOOL)showCustomCallout;
- (void) setShowCustomCallout:(BOOL)showCustomCallout animated:(BOOL)animated;
#end
and the .m file:
#import "CustomAnnotation.h"
#import "CustomAnnotationCallout.h"
#import "Utilities.h"
#import "UIImageView+WebCache.h"
#implementation CustomAnnotation
- (id) initWithTitle:(NSString *)newTitle Location:(CLLocationCoordinate2D)location UserImage:(NSString *)userImageString isCheckedIn:(BOOL)checkedIn{
self = [super init];
[[NSBundle mainBundle] loadNibNamed:#"AnnotationView" owner:self options:nil];
if (self) {
if (_loadedView) {
_loadedView.frame = CGRectMake(_loadedView.frame.origin.x, _loadedView.frame.origin.y, 80, 80);
[self addSubview:_loadedView];
self.userImageString = userImageString;
}
self.userImageString = userImageString;
_title = newTitle;
_coordinate = location;
self.isCheckedIn = checkedIn;
self.enabled = YES;
self.canShowCallout = NO;
self.image = [UIImage imageNamed:#"icon_clear"];
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, 80, 80);
self.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
return self;
}
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString*)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
[[NSBundle mainBundle] loadNibNamed:#"AnnotationView" owner:self options:nil];
if (_loadedView){
_loadedView.frame = CGRectMake(_loadedView.frame.origin.x, _loadedView.frame.origin.y, 80, 80);
[self addSubview:_loadedView];
}
self.enabled = YES;
self.canShowCallout = NO;
self.image = [UIImage imageNamed:#"icon_clear"];
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, 80, 80);
self.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
return self;
}
- (void) setShowCustomCallout:(BOOL)showCustomCallout {
[self setShowCustomCallout:showCustomCallout animated:NO];
}
- (void) setShowCustomCallout:(BOOL)showCustomCallout animated:(BOOL)animated {
if (_showCustomCallout == showCustomCallout) return;
_showCustomCallout = showCustomCallout;
void (^animationBlock)(void) = nil;
void(^completionBlock)(BOOL finished) = nil;
if (_showCustomCallout) {
self.calloutView.alpha = 0.0f;
animationBlock = ^ {
self.calloutView.alpha = 1.0f;
[self addSubview:self.calloutView];
};
} else {
animationBlock = ^{ self.calloutView.alpha = 0.0f; };
completionBlock = ^(BOOL finished) { [self.calloutView removeFromSuperview]; };
}
if (animated) {
[UIView animateWithDuration:0.2f animations:animationBlock completion:completionBlock];
} else {
animationBlock();
completionBlock(YES);
}
}
#end
Now, if we click on the annotation we get our custom callout:
MapViewController Code:
- (void) mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
if ([view isKindOfClass:CustomAnnotation.class]) {
CustomAnnotation *annotationView = (CustomAnnotation *)view;
CustomAnnotationCallout *callout = [CustomAnnotationCallout new];
callout.userImageString = annotationView.userImageString;
[callout.userImage sd_setImageWithURL:[NSURL URLWithString:callout.userImageString]];
[callout.nameLabel setText:[NSString stringWithFormat:#"%# #%#", annotationView.calloutUser.name, annotationView.calloutUser.username]];
[callout setUserID:annotationView.calloutUser.userID];
annotationView.calloutView = callout.loadedView;
annotationView.calloutView.center = CGPointMake(annotationView.bounds.size.width*0.5f, -annotationView.calloutView.bounds.size.height*0.5f);
[annotationView setShowCustomCallout:YES animated:YES];
CGRect annotationViewWithCalloutViewFrame = annotationView.frame;
annotationViewWithCalloutViewFrame.size.width += annotationView.calloutView.frame.size.width;
annotationViewWithCalloutViewFrame.size.height += annotationView.calloutView.frame.size.height;
CGRect mapRect = [self.mapView convertRegion:self.mapView.region toRectToView:self.mapView];
mapRect.origin.y = annotationView.frame.origin.y-annotationView.calloutView.frame.size.height;
mapRect.origin.x = annotationView.frame.origin.x+annotationView.calloutView.frame.origin.x;
MKCoordinateRegion finalRegion = [self.mapView convertRect:mapRect toRegionFromView:self.mapView];
[self.mapView setRegion:finalRegion animated:YES];
[self.mapView setScrollEnabled:NO];
[self.mapView setZoomEnabled:NO];
[self.reloadTimer invalidate];
}
}
CustomAnnotationCallout is also pulled in from a xib.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface CustomAnnotationCallout : UIView
#property (strong, nonatomic) IBOutlet UIView *loadedView;
#property (strong, nonatomic) IBOutlet UIImageView *userImage;
#property (assign, nonatomic) NSString *userImageString;
#property (strong, nonatomic) IBOutlet UILabel *nameLabel;
#property (strong, nonatomic) NSString *userID;
- (IBAction)profilePressed:(id)sender;
- (IBAction)button1:(id)sender;
- (IBAction)button2:(id)sender;
#end
and the .m:
#import "CustomAnnotationCallout.h"
#implementation CustomAnnotationCallout
- (instancetype)init {
[[NSBundle mainBundle] loadNibNamed:#"CustomAnnotationCallout" owner:self options:nil];
_loadedView.frame = CGRectMake(_loadedView.frame.origin.x, _loadedView.frame.origin.y, [[UIScreen mainScreen] bounds].size.width, 178.0);
return self;
}
- (IBAction)profilePressed:(id)sender {
}
- (IBAction)button1:(id)sender {
}
- (IBAction)button2:(id)sender {
}
#end
Now, when we select the annotation our custom callout changes and the map repositions itself to where I selected. And we get this view:
Now that you see that what I have does work, and what i'm going for. The problem that I'm having, is figuring out how to get the button(s) on the callout to perform a specific task when clicked.
I've googled and read about hitTest, but couldn't figure out anything specifically on how to implement it properly especially when looking to get it to work on specific elements.
I've been at this from start to finish this entire week and this is where I'm stuck. Please note I've been doing iOS development for a total of about 4 months so I'm still relatively new.

Related

Why custom view awakeFromNib init set not work in Objc

I have a custom view about alertView.
But when I init my alertView, it seem not set my awakeFromNib value of label.
What's wrong about my code?
I can't figure out this issue.
Thanks.
AlertView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface AlertView : UIView
#property (weak, nonatomic) IBOutlet UILabel *labTitle;
#property (weak, nonatomic) IBOutlet UILabel *labMessage;
#property (weak, nonatomic) IBOutlet UIButton *btnCancel;
#property (weak, nonatomic) IBOutlet UIButton *btnConfirm;
#end
AlertView.m
#import "AlertView.h"
#interface AlertView ()
#property (weak, nonatomic) IBOutlet UIView *headerView;
#end
#implementation AlertView
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(!self){
return nil;
}
self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] firstObject];
self.frame = frame;
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
// custom label text not show.
self.labTitle.text = #"123";
self.labMessage.text = #"456";
[self.btnCancel setTitle:#"789"; forState:UIControlStateNormal];
[self.btnConfirm setTitle:#"111"; forState:UIControlStateNormal];
self.btnCancel.layer.cornerRadius = 5.f;
self.btnConfirm.layer.cornerRadius = 5.f;
self.headerView.layer.cornerRadius = 10;
}
#end
Viewcontroller.m
#interface Viewcontroller ()
#property AlertView * alertView;
#end
#implementation Viewcontroller
- (void)viewDidLoad {
[self popupView];
}
- (void)popupView{
CGRect viewSize = CGRectMake(16, 100, [[UIScreen mainScreen] bounds].size.width - 16 * 2, self.height);
self.alertView = [[ResultAlertView alloc] initWithFrame:viewSize];
self.alertView.layer.cornerRadius = 10;
[self SetShadowForView:self.alertView];
[self.alertView.btnConfirm addTarget:self action:#selector(cancelStartUsingView:) forControlEvents:UIControlEventTouchUpInside];
[self.alertView.btnCancel addTarget:self action:#selector(cancelStartUsingView:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.alertView];
}
- (IBAction)cancelStartUsingView:(UIButton *)sender{
//Btn also not work.
NSLog(#"123");
}
#end
you can use:
- (void)layoutSubviews {
[super layoutSubviews];
self.labTitle.text = #"123";
self.labMessage.text = #"456";
[self.btnCancel setTitle:#"789" forState:UIControlStateNormal];
[self.btnConfirm setTitle:#"111"forState:UIControlStateNormal];
self.btnCancel.layer.cornerRadius = 5.f;
self.btnConfirm.layer.cornerRadius = 5.f;
self.headerView.layer.cornerRadius = 10;
}
and call
- (void)viewDidLoad {
dispatch_async(dispatch_get_main_queue(), ^{
[self popupView];
});
}

How do you correctly implement the function for shifting UITextFields above the users input, when it obscures the UITextField?

Upon finding Apple's documentation for UITextFields, I stumbled upon this article which details how one can make it so that a UITextField, housed within a UIScrollView, can be shifted upwards so that the user can view their input. However, whenever I try to implement this, the behaviour is never as described. It either does nothing or shifts everything downwards, regardless of which UITextField you choose to edit.
Here is my code, can someone diagnose it?:
#import "FourthViewController.h"
#import Firebase;
#import FirebaseAuth;
#import FirebaseDatabase;
#interface FourthViewController ()
{
UITextField *activeField;
}
#property (strong, nonatomic) FIRDatabaseReference *ref;
#property (weak, nonatomic) IBOutlet UIButton *logoutButton;
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (readonly, nonatomic, nullable) FIRApp *app;
#property (readonly, strong, nonatomic, nullable) FIRUser *currentUser;
#property(weak, nonatomic) NSArray *countryArray;
#property (weak, nonatomic) IBOutlet UITextField *usernameEntry;
#property (weak, nonatomic) IBOutlet UITextField *forenameEntry;
#property (weak, nonatomic) IBOutlet UITextField *surnameEntry;
#property (weak, nonatomic) IBOutlet UITextField *emailAddressEntry;
#property (weak, nonatomic) IBOutlet UITextField *countryEntry;
#property (weak, nonatomic) IBOutlet UITextField *homeAddressEntry;
#property (weak, nonatomic) IBOutlet UIButton *applyChangesButton;
#property (weak, nonatomic) IBOutlet UIImageView *profilePictureEntry;
#property (weak, nonatomic) IBOutlet UILabel *emailValidationCheck;
#property (weak, nonatomic) IBOutlet UIPickerView *countrySelector;
#end
#implementation FourthViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.ref = [[FIRDatabase database] reference];
FIRUser *user = [FIRAuth auth].currentUser;
if (user)
{
[[[[_ref child: #"Users"] child:user.uid] child:#"Username"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot)
{
if (snapshot.exists)
{
_usernameEntry.text = snapshot.value;
}
}];
_emailAddressEntry.text = user.email;
}
_countrySelector.delegate = self;
_countrySelector.dataSource = self;
self.logoutButton.layer.cornerRadius= 8;
self.logoutButton.layer.borderWidth = 1.5f;
self.logoutButton.layer.borderColor = [UIColor whiteColor].CGColor;
[_logoutButton addTarget:self action:#selector(logoutButtonHighlightBorder) forControlEvents:UIControlEventTouchDown];
[_logoutButton addTarget:self action:#selector(logoutButtonUnhighlightBorder) forControlEvents:UIControlEventTouchUpInside];
[_logoutButton addTarget:self action:#selector(logoutButtonUnhighlightBorder) forControlEvents:UIControlEventTouchDragExit];
self.applyChangesButton.layer.cornerRadius = 8;
_countrySelector.layer.cornerRadius = 8;
NSLocale *locale = [NSLocale currentLocale];
NSArray *countryArray = [NSLocale ISOCountryCodes];
NSMutableArray *sortedCountryArray = [[NSMutableArray alloc] init];
for (NSString *countryCode in countryArray)
{
NSString *displayNameString = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];
[sortedCountryArray addObject:displayNameString];
}
[sortedCountryArray sortUsingSelector:#selector(localizedCompare:)];
}
- (IBAction)applyChanges:(UIButton *)sender
{
NSString *userID = [FIRAuth auth].currentUser.uid;
NSString* username = _usernameEntry.text;
[[[_ref child:#"Users"] child:userID] setValue:#{#"Username": username}];
[[FIRAuth auth].currentUser updateEmail:_emailAddressEntry.text
completion:^(NSError * _Nullable error)
{
if(error)
{
[_emailValidationCheck setTextColor:[UIColor redColor]];
_emailValidationCheck.text = #"Email Address modification was unsuccessful";
}
else
{
[_emailValidationCheck setTextColor:[UIColor greenColor]];
_emailValidationCheck.text = #"Email Address modification was successful";
}
}];
}
- (IBAction)logout:(UIButton *)sender
{
NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status)
{
NSLog(#"Error signing out: %#", signOutError);
}
else
{
NSLog(#"Successfully Signout");
}
}
- (NSInteger)numberOfComponentsInPickerView:(nonnull UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(nonnull UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return _countryArray.count;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return _countryArray[row];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
_countryEntry.text = _countryArray[row];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.view endEditing:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (void)logoutButtonHighlightBorder
{
_logoutButton.layer.borderColor = [UIColor colorWithRed:0.61 green:0.00 blue:0.02 alpha:1.0].CGColor;
}
- (void)logoutButtonUnhighlightBorder
{
_logoutButton.layer.borderColor = [UIColor whiteColor].CGColor;
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGRect bkgndRect = activeField.superview.frame;
bkgndRect.size.height += kbSize.height;
[activeField.superview setFrame:bkgndRect];
[_scrollView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height) animated:YES];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
_scrollView.contentInset = contentInsets;
_scrollView.scrollIndicatorInsets = contentInsets;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
activeField = nil;
}
Updated Code:
#import "FourthViewController.h"
#import Firebase;
#import FirebaseAuth;
#import FirebaseDatabase;
#interface FourthViewController ()
{
UITextField *activeField;
}
#property (strong, nonatomic) FIRDatabaseReference *ref;
#property (weak, nonatomic) IBOutlet UIButton *logoutButton;
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (readonly, nonatomic, nullable) FIRApp *app;
#property (readonly, strong, nonatomic, nullable) FIRUser *currentUser;
#property(weak, nonatomic) NSArray *countryArray;
#property (weak, nonatomic) IBOutlet UITextField *usernameEntry;
#property (weak, nonatomic) IBOutlet UITextField *forenameEntry;
#property (weak, nonatomic) IBOutlet UITextField *surnameEntry;
#property (weak, nonatomic) IBOutlet UITextField *emailAddressEntry;
#property (weak, nonatomic) IBOutlet UITextField *countryEntry;
#property (weak, nonatomic) IBOutlet UITextField *homeAddressEntry;
#property (weak, nonatomic) IBOutlet UIButton *applyChangesButton;
#property (weak, nonatomic) IBOutlet UIImageView *profilePictureEntry;
#property (weak, nonatomic) IBOutlet UILabel *emailValidationCheck;
#property (weak, nonatomic) IBOutlet UIPickerView *countrySelector;
#end
#implementation FourthViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.ref = [[FIRDatabase database] reference];
FIRUser *user = [FIRAuth auth].currentUser;
[self registerForKeyboardNotifications];
if (user)
{
[[[[_ref child: #"Users"] child:user.uid] child:#"Username"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot)
{
if (snapshot.exists)
{
_usernameEntry.text = snapshot.value;
}
}];
_emailAddressEntry.text = user.email;
}
_countrySelector.delegate = self;
_countrySelector.dataSource = self;
self.logoutButton.layer.cornerRadius= 8;
self.logoutButton.layer.borderWidth = 1.5f;
self.logoutButton.layer.borderColor = [UIColor whiteColor].CGColor;
[_logoutButton addTarget:self action:#selector(logoutButtonHighlightBorder) forControlEvents:UIControlEventTouchDown];
[_logoutButton addTarget:self action:#selector(logoutButtonUnhighlightBorder) forControlEvents:UIControlEventTouchUpInside];
[_logoutButton addTarget:self action:#selector(logoutButtonUnhighlightBorder) forControlEvents:UIControlEventTouchDragExit];
self.applyChangesButton.layer.cornerRadius = 8;
_countrySelector.layer.cornerRadius = 8;
NSLocale *locale = [NSLocale currentLocale];
NSArray *countryArray = [NSLocale ISOCountryCodes];
NSMutableArray *sortedCountryArray = [[NSMutableArray alloc] init];
for (NSString *countryCode in countryArray)
{
NSString *displayNameString = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];
[sortedCountryArray addObject:displayNameString];
}
[sortedCountryArray sortUsingSelector:#selector(localizedCompare:)];
}
- (IBAction)applyChanges:(UIButton *)sender
{
NSString *userID = [FIRAuth auth].currentUser.uid;
NSString* username = _usernameEntry.text;
[[[_ref child:#"Users"] child:userID] setValue:#{#"Username": username}];
[[FIRAuth auth].currentUser updateEmail:_emailAddressEntry.text
completion:^(NSError * _Nullable error)
{
if(error)
{
[_emailValidationCheck setTextColor:[UIColor redColor]];
_emailValidationCheck.text = #"Email Address modification was unsuccessful";
}
else
{
[_emailValidationCheck setTextColor:[UIColor greenColor]];
_emailValidationCheck.text = #"Email Address modification was successful";
}
}];
}
- (IBAction)logout:(UIButton *)sender
{
NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status)
{
NSLog(#"Error signing out: %#", signOutError);
}
else
{
NSLog(#"Successfully Signout");
}
}
- (NSInteger)numberOfComponentsInPickerView:(nonnull UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(nonnull UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return _countryArray.count;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return _countryArray[row];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
_countryEntry.text = _countryArray[row];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.view endEditing:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (void)logoutButtonHighlightBorder
{
_logoutButton.layer.borderColor = [UIColor colorWithRed:0.61 green:0.00 blue:0.02 alpha:1.0].CGColor;
}
- (void)logoutButtonUnhighlightBorder
{
_logoutButton.layer.borderColor = [UIColor whiteColor].CGColor;
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat offset = CGRectGetMaxY(activeField.frame) - CGRectGetMinY(kbFrame);
[_scrollView setContentOffset:CGPointMake(0.0, offset) animated:YES];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
_scrollView.contentInset = contentInsets;
_scrollView.scrollIndicatorInsets = contentInsets;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
activeField = nil;
}
#end
What you want to do is shift the bottom edge of your textField directly above the keyboard. This is done by just adjusting the scrollView's contentOffset the right way. You already do it almost right as I can see. The only thing that might be wrong is that you change CGRect bkgndRect = activeField.superview.frame; what is not necessary. You could try the following approach where minY/maxY are frame.origin.y/(frame.origin.y+frame.size.height).
- (void)keyboardWillShow:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat offset = CGRectGetMaxY(activeTextField.frame) - CGRectGetMinY(kbFrame)
_scrollView.contentOffset = CGPointMake(0.0, offset);
}
- (void)keyboardWillHide:(NSNotification*)aNotification
{
_scrollView.contentOffset = CGPointZero;
}
Additionally, you can also check if the calculated offset is > 0 since in this case, the text field is already above the keyboard.
Also when the keyboard will be hidden I think I never updated the scroll indicator insets. Just reset the content offset of the scroll view.
If you need this code some day for Swift 4++ - here is an example for this too which does not use auto layout. Actually, I tested this code and it works:
class ViewController: UIViewController {
var willShow: NSObjectProtocol?
var willHide: NSObjectProtocol?
let scroll = UIScrollView()
let txt = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
scroll.frame = view.bounds
scroll.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(scroll)
txt.frame = CGRect(x: 0, y: scroll.bounds.height-100, width: scroll.bounds.width, height: 50)
txt.text = "Hello World"
txt.backgroundColor = .lightGray
txt.translatesAutoresizingMaskIntoConstraints = true
scroll.addSubview(txt)
willShow = NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: .main) { (note) in
let kbFrame = note.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect ?? .zero
self.scroll.contentOffset = CGPoint(x: 0, y: self.txt.frame.maxY - kbFrame.minY )
}
willHide = NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: .main) { (note) in
self.scroll.contentOffset = .zero
}
view.addGestureRecognizer(UITapGestureRecognizer(target: txt, action: #selector(resignFirstResponder)))
}
}
Hope that helps 😉

Pass info to multiple viewControllers via iCarousel

I'm trying to implement an iCarousel that will pass information on to two other view controllers when an image is chosen. While the iCarousel loads perfectly and transitions to the next VC, the information is not displayed on the new VC.
The approach I chose was to create an NSObject file. I can't simply pass the info from VC to VC since I have several VC's that need the information and I'd prefer not to create a singleton or use AppDelegate if possible.
FYI: I do have a tap gesture recognizer added on top of the UIView that acts as the segue to the next VC if that makes any difference.
I've tried every possible tutorial out there and can't seem to figure out my problem. I just need to display a text label and a picture, which should really be pretty easy. Can someone take a quick glance at my code to see what I'm doing wrong?
My NSObject File:
#import <Foundation/Foundation.h>
#interface Stop : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *image;
#end
First ViewController.h (with iCarousel on it):
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "iCarousel.h"
#import "DirectionsViewController.h"
#import "Stop.h"
#interface StopsMenuViewController : UIViewController <iCarouselDataSource, iCarouselDelegate>
#property (strong, nonatomic) IBOutlet iCarousel *carousel;
#property (strong, nonatomic) IBOutlet UILabel *titleLabel;
//Title
#property (nonatomic, strong) NSArray *stopTitles;
#property (nonatomic, strong) NSString *stopChosen;
//Image
#property (nonatomic, strong) NSArray *stopImages;
#property (nonatomic, strong) NSString *imageChosen;
#end
First ViewController.m:
#import "StopsMenuViewController.h"
#interface StopsMenuViewController () {
NSMutableArray *allInfo; }
#end
#implementation StopsMenuViewController
#synthesize titleLabel, carousel, stopImages, stopTitles, stopChosen, imageChosen;
- (void)awakeFromNib {
NSString *myPlist = [[NSBundle mainBundle] pathForResource:#"Chinatown" ofType:#"plist"];
NSDictionary *rootDictionary = [[NSDictionary alloc] initWithContentsOfFile:myPlist];
self.stopImages = [rootDictionary objectForKey:#"StopImages"];
self.stopTitles = [rootDictionary objectForKey:#"StopTitles"];
}
- (void)carouselDidScroll:(iCarousel *)carousel {
[titleLabel setText:[NSString stringWithFormat:#"%#", [self.stopTitles
objectAtIndex:self.carousel.currentItemIndex]]];
}
- (void)dealloc {
self.carousel.delegate = nil;
self.carousel.dataSource = nil;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"toDirections"])
{
DirectionsViewController *dvc = [segue destinationViewController];
int itemId = [self.carousel currentItemIndex];
NSIndexPath *path = [NSIndexPath indexPathForRow:itemId inSection:0];
Stop *current = [allInfo objectAtIndex:path.row];
[dvc setPassInfo:current];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
self.carousel.type = iCarouselTypeCoverFlow2;
allInfo = [[NSMutableArray alloc] init];
Stop *info = [[Stop alloc] init];
stopChosen = [NSString stringWithFormat:#"%#", [self.stopTitles objectAtIndex:self.carousel.currentItemIndex]];
[info setTitle:stopChosen];
[allInfo addObject:info];
info = [[Stop alloc] init];
self.imageChosen = [NSString stringWithFormat:#"%#", [self.stopImages
objectAtIndex:self.carousel.currentItemIndex]];
[info setTitle:self.imageChosen];
[allInfo addObject:info];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.carousel = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel {
return [self.stopImages count];
}
- (NSUInteger)numberOfVisibleItemsInCarousel:(iCarousel *)carousel {
return 4;
}
- (UIView *)carousel:(iCarousel *)_carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view {
if (view == nil)
{
view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.stopImages objectAtIndex:index]]];
}
return view;
}
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index {
DirectionsViewController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"dvc"];
[self.navigationController pushViewController:dvc animated:YES];
}
#end
Second ViewController.h:
#import <UIKit/UIKit.h>
#import "Stop.h"
#interface DirectionsViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *titleLabel;
#property (strong, nonatomic) IBOutlet UIImageView *imageBox;
#property (nonatomic, strong) Stop *PassInfo;
#property (nonatomic, strong) NSString *stopTitle;
#property (nonatomic, strong) NSString *myStopTitle;
#end
Second ViewController.m:
#import "DirectionsViewController.h"
#interface DirectionsViewController ()
#end
#implementation DirectionsViewController
#synthesize PassInfo;
- (void)viewDidLoad {
[super viewDidLoad];
[self.titleLabel setText:[PassInfo title]];
UIImage *image = [UIImage imageNamed:[PassInfo image]];
[self.imageBox setImage:image];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Instead of
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index {
DirectionsViewController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"dvc"];
[self.navigationController pushViewController:dvc animated:YES];
}
Use
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index {
[self performSegueWithIdentifier:#"toDirections" sender:self];
}
In your code, you're instantiating the second view controller and presenting it, which is not the same as performing a segue. Therefore the method - prepareForSegue:sender: will not be invoked.

MKPinAnnotationView not working

I would like to have a MKMapView showing annotations with disclosure-buttons which lead to a view controller like the Golden Gate Bridge annotation in this Apple sample app.
I load the coordinates from a plist and the annotations appear correctly with title/subtitle but the method
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
has no effect.
I guess that I somehow have to link the annotations with the pinannotations?
MapViewController.h:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "Annotation.h"
#interface MapViewController : UIViewController<CLLocationManagerDelegate, MKMapViewDelegate>
#property (strong, nonatomic) CLLocationManager *location;
#property (nonatomic, retain) NSArray *data;
#end
MapViewController.m:
#import "MapViewController.h"
#interface MapViewController ()
#property (nonatomic, weak) IBOutlet MKMapView *mapView;
#end
#implementation MapViewController
#synthesize data;
#synthesize location, minLatitude, maxLatitude, minLongitude, maxLongitude;
- (void)viewDidLoad
{
NSString *dataPath = [[NSBundle mainBundle] pathForResource:#"City" ofType:#"plist"];
self.data = [NSArray arrayWithContentsOfFile:dataPath];
for (int i = 0; i < data.count; i++) {
NSDictionary *dataItem = [data objectAtIndex:i];
//Create Annotation
Annotation *building = [[Annotation alloc] init];
building.title = [dataItem objectForKey:#"Title"];
building.subtitle = [dataItem objectForKey:#"Subtitle"];
MKCoordinateRegion buildingcoordinates = { {0.0, 0.0}, {0.0, 0.0} };
buildingcoordinates.center.latitude = [[dataItem objectForKey:#"Latitude"] floatValue];
buildingcoordinates.center.longitude = [[dataItem objectForKey:#"Longitude"] floatValue];
building.coordinate = buildingcoordinates.center;
[self.mapView addAnnotation:building];
}
[super viewDidLoad];
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *pinIdentifier = #"pinIndentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
if (pinView == nil)
{
// if an existing pin view was not available, create one
MKPinAnnotationView *customPinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
customPinView.pinColor = MKPinAnnotationColorPurple;
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
Annotation.h:
#import <Foundation/Foundation.h>
#import <MapKit/MKAnnotation.h>
#interface Annotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
#property(nonatomic, assign) CLLocationCoordinate2D coordinate;
#property(nonatomic, copy) NSString *title;
#property(nonatomic, copy) NSString *subtitle;
#end
Annotation.m:
#import "Annotation.h"
#implementation Annotation
#synthesize coordinate, title, subtitle;
#end
Most likely the map view's delegate is not set which means the viewForAnnotation delegate method will not get called.
Since you've declared mapView as an IBOutlet, in the xib, make sure that the map view's delegate is connected to File's Owner.
Alternatively, at the top of the viewDidLoad method in MapViewController, set it programmatically:
mapView.delegate = self;

dynamically generated UIButton in iOS 5 ARC causes deallocated instance crash

I have a a class I created to generate UIButton's I add to my UIView. This worked great until my conversion to ARC yesterday, not I get the following error:
-[OrderTypeButton performSelector:withObject:withObject:]: message sent to deallocated instance 0x12449f70
Here is the code to add the button to my UIView (actually a subview in my main UIView):
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders" withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]] hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
And here is my class header:
#import <UIKit/UIKit.h>
#protocol OrderTypeButtonDelegate
-(void) tapped:(id)sender withOrderType:(NSString*) orderType;
#end
#interface OrderTypeButton : UIViewController {
id<OrderTypeButtonDelegate> __unsafe_unretained delegate;
IBOutlet UILabel *lblOrderType;
IBOutlet UILabel *lblOrderCount;
NSString *orderType;
NSString *orderCount;
BOOL hasOpenOrder;
}
#property (nonatomic, strong) IBOutlet UIButton *orderButton;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderType;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderCount;
#property (nonatomic, strong) NSString *orderType;
#property (nonatomic, strong) NSString *orderCount;
#property (nonatomic, assign) BOOL hasOpenOrder;
#property (nonatomic, unsafe_unretained) id<OrderTypeButtonDelegate> delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder;
-(IBAction)btnTapped:(id)sender;
#end
Implementation:
#import "OrderTypeButton.h"
#implementation OrderTypeButton
#synthesize orderButton;
#synthesize lblOrderType, lblOrderCount, orderType, orderCount, hasOpenOrder, delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder {
if ((self = [super init])) {
self.orderType = anOrderType;
self.orderCount = anOrderCount;
self.hasOpenOrder = openOrder;
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.lblOrderType.text =[NSString stringWithFormat:#"%#", self.orderType];
self.lblOrderCount.text = [NSString stringWithFormat:#"%#", self.orderCount];
if (self.hasOpenOrder) {
[self.orderButton setBackgroundImage:[UIImage imageNamed:#"background-order-btn-red.png"] forState:UIControlStateNormal];
self.lblOrderType.textColor = [UIColor whiteColor];
self.lblOrderCount.textColor = [UIColor whiteColor];
}
}
-(IBAction)btnTapped:(id)sender {
NSLog(#"TAPPED");
if ([self delegate] ) {
[delegate tapped:sender withOrderType:self.orderType];
}
}
- (void)viewDidUnload
{
[self setOrderButton:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
This seems fairly simple what I am doing here, not sure what changed with ARC that is causing me problems.
Maybe ARC autorelease created button, try to store created buttons in Array
//.h file
#property (nonatomic, strong) NSArray *buttonsArray
//.m file
#synthesize buttonsArray
...
- (void)viewDidLoad {
buttonsArray = [NSArray array];
...
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders"
withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]]
hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
//Add button to array
[buttonsArray addObject:btn];
Also this approach will help if you want to change buttons, or remove some specific button from view

Resources