Two UIScrollViews, synchronous scrolling - iphone

I heve two UIScrollViews, they are on top of each other.
UIView
|
--------------------------
| |
UIScrollView1 UIScrollView2
I would like it, to work in the following way. If I scroll UIScrollView2, UIScrollView1 should also scroll by the same contentOffset. It must by done synchronously, so using scrollViewDidScroll is not an option. Do you guys have some idea, how can it be done?
Source Code
_prContentGridView = [[PRContentGridView alloc] initWithFrame:frame];
_prContentGridView.minimumZoomScale = 0.25;
_prContentGridView.maximumZoomScale = 2.0;
_prContentGridView.delegate = self;
_prBackgroundGridView = [[PRBackgroundGridView alloc] initWithFrame:frame];
[self addSubview:_prBackgroundGridView];
[self addSubview:_prContentGridView];
Delegate Method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_prContentGridView.scrollEnabled == YES) {
CGPoint p = CGPointMake(scrollView.contentOffset.x - _prevousContentOffsetOfContentScrollView.x, scrollView.contentOffset.y - _prevousContentOffsetOfContentScrollView.y);
[_prBackgroundGridView setContentOffset:p animated:YES];
}
}

use the UIScrollViewDelegate Protocol method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView == UIScrollView1){
UIScrollView2.contentOffset = scrollView.contentOffset;
}else{
UIScrollView1.contentOffset = scrollView.contentOffset;
}
}

You should try this Code,
first Declare IBOutlet in .h File,
IBOutlet UIScrollView *FirstScrollView;
IBOutlet UIScrollView *SecondScrollView;
then try this code,
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([scrollView isEqual: FirstScrollView])
{
SecondScrollView.contentOffset =
CGPointMake(FirstScrollView.contentOffset.x, 0);
}
else
{
FirstScrollView.contentOffset =
CGPointMake(SecondScrollView.contentOffset.x, 0);
}
}

Related

Dismissing UIPickerView on tapping background

I am adding a uipickerview as the subview of the main view. To dismiss the pickerview on tapping the backgroud view, i am adding a UITapGestureRecognizer to the main view.
I am using the following code to add the GestureRecognizer for main view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
gestureRecognizer.numberOfTapsRequired=1;
gestureRecognizer.numberOfTouchesRequired=1;
gestureRecognizer.delegate = self;
[self.view addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
In the handleSingleTap method i am dismissing the pickerview.
But the problem is handleSingleTap is also called when I tap inside the pickerview. To avoid it i used the following delegate method of UIGestureRecognizer
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
/*
*If the tap is inside a button return NO, to ensure the button click is detected.
*/
if ([touch.view isKindOfClass:[UIButton class]]){
return FALSE;
}else if([touch.view isKindOfClass:[UIPickerView class]]) {
return FALSE;
}
return TRUE;
}
It is working for button,But is not working for UIPickerView. Can anyone help me with this?
I have coded up a solution to your particular requirement.
first, i implemented your code as you have described and observed the same problem you reported - spurious tap events being sent to tap handler, when you tapped on anything, including a UIButton.
this told me that the UITapGestureRecogniser was "stealing" the touches that should have gone to the UIButton, so i decided the simplest, most pragmatic solution was to use that feature to my advantage, and so i assigned a UITapGestureRecogniser to both the pickerview and the button also. the taps for the pickerview we just discard, the others we parse and pass on to the button's tap handler.
note - for expedience i assigned the pickerview's datasource and delegate in the xib. you will need to do that also, or set it in code.
header
//
// ViewController.h
// stackExchangeDemo
//
// Created by unsynchronized on 18/01/12.
// released to public domain via http://stackoverflow.com/a/8908028/830899
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>
{
UIButton *btn1;
UIPickerView *picker1;
}
#property (retain, nonatomic) IBOutlet UIButton *btn1;
#property (retain, nonatomic) IBOutlet UIPickerView *picker1;
#end
implementation
//
// ViewController.m
// stackExchangeDemo
//
// Created by unsynchronized on 18/01/12.
// released to public domain via http://stackoverflow.com/a/8908028/830899
//
#import "ViewController.h"
#implementation ViewController
#synthesize btn1;
#synthesize picker1;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
-(void) handleSingleTap:(UITapGestureRecognizer *) tapper {
if (tapper.state == UIGestureRecognizerStateEnded) {
NSLog(#"%#",NSStringFromSelector(_cmd));
}
}
- (IBAction)handleButtonTap:(id)sender {
NSLog(#"%#",NSStringFromSelector(_cmd));
}
-(void) handleButtonTapGesture:(UITapGestureRecognizer *) tapper {
// call the buttons event handler
UIControlEvents eventsToHandle = UIControlEventTouchUpInside;
if (tapper.state == UIGestureRecognizerStateEnded) {
UIButton *btn = (UIButton *) tapper.view;
for (NSString *selName in [btn actionsForTarget:self forControlEvent:eventsToHandle]) {
SEL action = NSSelectorFromString(selName);
if (action) {
[self performSelector:action withObject:btn1];
break;
}
};
}
}
-(void) handleDummyTap:(UITapGestureRecognizer *) tapper {
// silently ignore the tap event for this view.
}
-(void) setupTap:(UIView *) view action:(SEL)action {
// assign custom tap event handler for given view.
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:action];
[view addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setupTap:self.view action:#selector(handleSingleTap:)];
[self setupTap:picker1 action:#selector(handleDummyTap:)];
[self setupTap:btn1 action:#selector(handleButtonTapGesture:)];
}
- (void)viewDidUnload
{
[self setBtn1:nil];
[self setPicker1:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark #protocol UIPickerViewDataSource<NSObject>
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return 1;
}
#pragma mark #protocol UIPickerViewDelegate<NSObject>
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return [#"so long and thanks for all the fish".copy autorelease ];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
NSLog(#"%#",NSStringFromSelector(_cmd));
}
- (void)dealloc {
[btn1 release];
[picker1 release];
[super dealloc];
}
#end
It is possible that the view touched (touch.view) is one of the subviews of the pickerview. I'd try testing:
[[pickerview subviews] containsObject: touch.view];
I implemented the following in Swift:
override func viewDidLoad()
{
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
func handleTapGesture(sender: AnyObject)
{
let subview = view?.hitTest(sender.locationInView(view), withEvent: nil)
if(!(subview?.isDescendantOfView(timePicker) ?? false))
{//might want to add a condition to make sure it's not your button ^^
showTimePicker(false)//method which handles showing/hiding my picker
}
}
I simply added an invisible UIControl instance behind the UIPickerView, which covers all the window, and gets all the touches behind UIPickerView. If it is touched, then both the UIPickerView and the UIControl is dismissed. (SelectButton and CancelButton are accessory buttons to UIPickerView.)
#property (strong, nonatomic) UIControl *touchRecognizer;
- (IBAction)showPicker:(id)sender {
self.touchRecognizer = [[UIControl alloc]initWithFrame:self.view.window.bounds];
[self.view.window addSubview:self.touchRecognizer];
[self.touchRecognizer addTarget:self action:#selector(touchedOutsidePicker:) forControlEvents:UIControlEventTouchUpInside];
[self.textField becomeFirstResponder];
}
- (IBAction)touchedOutsidePicker:(id)sender {
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}
-(void)selectButtonPressed:(id)sender{
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}
-(void)cancelButtonPressed:(id)sender{
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}

How to add annotation on center of map view in iPhone?

I have MAP view in my app and i want to add Annotation pin (Red pin) on center of map view.
Now when user scroll map view pin should adjust with center according to that.
How to do that?
Thank
If you want to use an actual annotation instead of just a regular view positioned above the center of the map view, you can:
use an annotation class with a settable coordinate property (pre-defined MKPointAnnotation class eg). This avoids having to remove and add the annotation when the center changes.
create the annotation in viewDidLoad
keep a reference to it in a property, say centerAnnotation
update its coordinate (and title, etc) in the map view's regionDidChangeAnimated delegate method (make sure map view's delegate property is set)
Example:
#interface SomeViewController : UIViewController <MKMapViewDelegate> {
MKPointAnnotation *centerAnnotation;
}
#property (nonatomic, retain) MKPointAnnotation *centerAnnotation;
#end
#implementation SomeViewController
#synthesize centerAnnotation;
- (void)viewDidLoad {
[super viewDidLoad];
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = mapView.centerCoordinate;
pa.title = #"Map Center";
pa.subtitle = [NSString stringWithFormat:#"%f, %f", pa.coordinate.latitude, pa.coordinate.longitude];
[mapView addAnnotation:pa];
self.centerAnnotation = pa;
[pa release];
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
centerAnnotation.coordinate = mapView.centerCoordinate;
centerAnnotation.subtitle = [NSString stringWithFormat:#"%f, %f", centerAnnotation.coordinate.latitude, centerAnnotation.coordinate.longitude];
}
- (void)dealloc {
[centerAnnotation release];
[super dealloc];
}
#end
Now this will move the annotation but not smoothly. If you need the annotation to move more smoothly, you can add a UIPanGestureRecognizer and UIPinchGestureRecognizer to the map view and also update the annotation in the gesture handler:
// (Also add UIGestureRecognizerDelegate to the interface.)
// In viewDidLoad:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
panGesture.delegate = self;
[mapView addGestureRecognizer:panGesture];
[panGesture release];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
pinchGesture.delegate = self;
[mapView addGestureRecognizer:pinchGesture];
[pinchGesture release];
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
centerAnnotation.coordinate = mapView.centerCoordinate;
centerAnnotation.subtitle = [NSString stringWithFormat:#"%f, %f", centerAnnotation.coordinate.latitude, centerAnnotation.coordinate.longitude];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
//let the map view's and our gesture recognizers work at the same time...
return YES;
}
Anna's answer is clever approach to keeping an annotation centered in the map using the annotations in the standard mapview way. However, as one commenter pointed out, the scrolling and pinching while much better still shows noticeable lag, and the recommended approach would be to add the annotation view as a subview of the mapview. Here's what that looks like.
#interface SHCenterPinMapViewController () <MKMapViewDelegate>
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (strong, nonatomic) MKPointAnnotation *centerAnnotaion;
#property (strong, nonatomic) MKPinAnnotationView *centerAnnotationView;
#end
#implementation SHCenterPinMapViewController
- (MKPointAnnotation *)centerAnnotaion
{
if (!_centerAnnotaion) {
_centerAnnotaion = [[MKPointAnnotation alloc] init];
}
return _centerAnnotaion;
}
- (MKPinAnnotationView *)centerAnnotationView
{
if (!_centerAnnotationView) {
_centerAnnotationView = [[MKPinAnnotationView alloc] initWithAnnotation:self.centerAnnotaion
reuseIdentifier:#"centerAnnotationView"];
}
return _centerAnnotationView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
[self.mapView addSubview:self.centerAnnotationView];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self moveMapAnnotationToCoordinate:self.mapView.centerCoordinate];
}
// These are the constants need to offset distance between the lower left corner of
// the annotaion view and the head of the pin
#define PIN_WIDTH_OFFSET 7.75
#define PIN_HEIGHT_OFFSET 5
- (void)moveMapAnnotationToCoordinate:(CLLocationCoordinate2D) coordinate
{
CGPoint mapViewPoint = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
// Offset the view from to account for distance from the lower left corner to the pin head
CGFloat xoffset = CGRectGetMidX(self.centerAnnotationView.bounds) - PIN_WIDTH_OFFSET;
CGFloat yoffset = -CGRectGetMidY(self.centerAnnotationView.bounds) + PIN_HEIGHT_OFFSET;
self.centerAnnotationView.center = CGPointMake(mapViewPoint.x + xoffset,
mapViewPoint.y + yoffset);
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
self.centerAnnotaion.coordinate = mapView.centerCoordinate;
[self moveMapAnnotationToCoordinate:mapView.centerCoordinate];
}
#end
Really the only interesting thing to not is that you have to offset the MKPinAnnotationView by a certain amount to account for the distance between the lower left corner and the pin head. I don't like having these asset dependent constants in the code, so if anyone can find a better way to do that, I am all ears.
I created a github project with a map controller that does this as well as some other things related to using a mapview to have a user select a location. Check it out here: https://github.com/scottrhoyt/CenterPinMapViewController
Swift version
In class:
var centerAnnotation = MKPointAnnotation()
var centerAnnotationView = MKPinAnnotationView()
In viewDidLoad:
if #available(iOS 9.0, *) {
centerAnnotationView.pinTintColor = customColor
} else {
// Fallback on earlier versions
centerAnnotationView.pinColor = MKPinAnnotationColor.Red
}
self.view.addSubview(centerAnnotationView)
In viewDidAppear:
self.moveMapAnnotationToCoordinate(self.mapView.centerCoordinate)
Then:
func moveMapAnnotationToCoordinate(locate : CLLocationCoordinate2D ) {
let mapViewPoint : CGPoint = self.mapView.convertCoordinate(locate, toPointToView: self.mapView)
let pinWidth : CGFloat = 7.75
let pinHeight : CGFloat = 7
let xOffset : CGFloat = CGRectGetMidX(self.centerAnnotationView.bounds) - pinWidth
let yOffset : CGFloat = CGRectGetMidY(self.centerAnnotationView.bounds) - pinHeight
self.centerAnnotationView.center = CGPointMake(mapViewPoint.x - xOffset, mapViewPoint.y - yOffset)
}
For using change in location, add delegate CLLocationManagerDelegate and then:
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
self.centerAnnotation.coordinate = self.mapView.centerCoordinate
self.moveMapAnnotationToCoordinate(self.mapView.centerCoordinate)
}

UIView not retaining subviews

I made this play/pause/stop control but it doesn't work as expected. at load time it creates three views and stores them in an array, each view represents one state of the control. In the stopped state it contains a play button that is a member of a simple class cluster I made. In the other two states it contains a UIView with two of the buttons as subviews. In the first state it works and does exactly what it's supposed to, but when it tries to go to the next state it looks in the array and finds views at the playing state and paused state positions with no subviews. In fact, if you trace it through the execution of the loadView function the array never gets a view with subviews in it even though I called addSubview:(UIView *)view which the documentation says this about: This method retains view and sets its next responder to the receiver, which is its new superview.
I would really like some help trying to understand why this is happening. To be more clear, why don't the UIViews that are passed to the array have subviews when the local variables for them do.
Thanks in advance,
Rich
Here's the source:
// IMSpeechControl.h
#import <UIKit/UIKit.h>
#import "IMSpeechEngine.h"
typedef enum {
IMSpeechControlStateStopped = 0,
IMSpeechControlStatePlaying = 1,
IMSpeechControlStatePaused = 2
} IMSpeechControlState;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
#class IMSpeechControl;
#protocol IMSpeechControlDelegate <NSObject>
- (NSString *)speechControlNeedsText:(IMSpeechControl *)sender;
#end
#interface IMSpeechControl : UIViewController {
IMSpeechControlState controlState;
id delegate;
IMSpeechEngine *speechEngine;
NSMutableArray *controlButtons_;
CGRect frame_;
}
#property (nonatomic, readonly) IMSpeechControlState controlState;
#property (nonatomic, assign) id<IMSpeechControlDelegate> delegate;
// Designated initilazer
- (IMSpeechControl *)initWithFrame:(CGRect)frame;
// This must be here, do not call it from outside it's control buttons
- (void)changeToState:(IMSpeechControlState)controlState;
- (void)play;
- (void)pause;
- (void)stop;
#end
This is the important one.
// IMSpeechControl.m
#import "IMSpeechControl.h"
#import "IMSpeechControlButton.h"
#interface IMSpeechControl ()
#property (nonatomic, assign) IMSpeechEngine *speechEngine;
#property (nonatomic, retain) NSMutableArray *controlButtons;
// Used only for initialization, do not change after calling initWithFrame
// to change the view size after creation
#property (nonatomic) CGRect frame;
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation;
#end
#implementation IMSpeechControl
#synthesize controlState, delegate, speechEngine, frame=frame_;
#synthesize controlButtons=controlButtons_;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
- (IMSpeechControl *)initWithFrame:(CGRect)aFrame {
if (self = [super init]) {
self.frame = aFrame;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(speechEngineDidFinishSpeaking:) name:kDidFinishSpeakingNotificationName object:self.speechEngine];
}
return self;
}
- (void)loadView {
// Initialization code.
// Create the main view.
UIView *aView = [[UIView alloc] initWithFrame:self.frame];
self.view = aView;
[aView release];
// Create the sub-views and store them in an array.
NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:3];
// The stopped state play button view can be used directly since it is the only button shown.
IMSpeechControlButton *button = [[IMSpeechControlButton alloc] initWithFrame:self.frame forControl:self style:IMSpeechControlButtonStylePlay];
[controls insertObject:button atIndex:(NSUInteger)IMSpeechControlStateStopped];
[button release];
// The other two states require two buttons each, so the two buttons must be grouped into a UIView that can be easily switched out.
// Make two frames, one for the left and one for the right. Both are half the width of the main view
// The one on the left has the same origin as the main view...
CGRect halfFrameLeft = CGRectMake(frame_.origin.x, frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// and the one on the right starts half-way across the main view
CGRect halfFrameRight = CGRectMake((frame_.origin.x + (frame_.size.width / 2)), frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// Playing state
// Pause button
UIView *playingState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *plsPauseButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePause];
[playingState addSubview:plsPauseButton];
[plsPauseButton release];
// Stop button
IMSpeechControlButton *plsStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[playingState addSubview:plsStopButton];
[plsStopButton release];
[controls insertObject:playingState atIndex:(NSUInteger)IMSpeechControlStatePlaying];
[playingState release];
// Paused state
// Play button
UIView *pausedState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *pasPlayButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePlay];
[pausedState addSubview:pasPlayButton];
[pasPlayButton release];
// Stop button
IMSpeechControlButton *pasStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[pausedState addSubview:pasStopButton];
[pasStopButton release];
[controls insertObject:pausedState atIndex:(NSUInteger)IMSpeechControlStatePaused];
[pausedState release];
// store the array in an instance variable
self.controlButtons = controls;
[controls release];
// Set the view to it's first state (stopped)
IMSpeechControlButton *stoppedState = (IMSpeechControlButton *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
controlState = IMSpeechControlStateStopped;
}
- (IMSpeechEngine *)speechEngine {
if (nil == speechEngine) {
self.speechEngine = [IMSpeechEngine sharedManager];
}
return speechEngine;
}
- (void)changeToState:(IMSpeechControlState)state {
// This line caused my problem
// IMSpeechControlButton *currentView = [[self.view subviews] objectAtIndex:0];
// It should look like this
UIView *currentView = [[self.view subviews] objectAtIndex:0];
switch (state) {
case IMSpeechControlStateStopped:
{
UIView *stoppedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
stoppedState.alpha = 0.15;
}
completion:^(BOOL finished)
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{stoppedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStateStopped;
break;
}
case IMSpeechControlStatePlaying:
{
UIView *playingState = [self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePlaying];
[self.view addSubview:playingState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
playingState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{playingState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePlaying;
break;
}
case IMSpeechControlStatePaused:
{
UIView *pausedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePaused];
[self.view addSubview:pausedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
pausedState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{pausedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePaused;
break;
}
default:
NSLog(#"Error %lu is not a recognized IMSpeechControlState", state);
break;
}
}
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation {
// This notification is only sent if it has finished speaking and is therefore stopped.
[self changeToState:IMSpeechControlStateStopped];
}
- (void)play {
NSString *text = [delegate speechControlNeedsText:self];
[self.speechEngine speakText:text];
[self changeToState:IMSpeechControlStatePlaying];
}
- (void)pause {
[self.speechEngine pauseSpeaking];
[self changeToState:IMSpeechControlStatePaused];
}
- (void)stop {
[self.speechEngine stopSpeaking];
[self changeToState:IMSpeechControlStateStopped];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:kDidFinishSpeakingNotificationName];
[super dealloc];
}
#end
// IMSpeechControlButton.h
#import "IMSpeechControl.h"
typedef enum {
IMSpeechControlButtonStylePlay,
IMSpeechControlButtonStylePause,
IMSpeechControlButtonStyleStop
}IMSpeechControlButtonStyle;
#interface IMSpeechControlButton: UIView {
IMSpeechControl *speechControl;
UIImageView *imageView;
}
#property (nonatomic, assign) IMSpeechControl *speechControl;
#property (nonatomic, retain) UIImageView *imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style;
#end
// IMSpeechControlButton.m
#import "IMSpeechControlButton.h"
#import <Foundation/NSObjCRuntime.h>
#implementation IMSpeechControlButton
#synthesize speechControl;
#synthesize imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
{
NSString *str;
switch (style) {
case IMSpeechControlButtonStylePlay:
str = #"IMSpeechControlPlay";
break;
case IMSpeechControlButtonStylePause:
str = #"IMSpeechControlPause";
break;
case IMSpeechControlButtonStyleStop:
str = #"IMSpeechControlStop";
break;
default:
break;
}
isa = NSClassFromString(str);
// the speechControl must be set before calling subclass implementation of initWithFrame
self.speechControl = control;
return [self initWithFrame:aRect];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
All the control buttons have the exact same code as the play button, except that the handleGesture method calls the appropriate play/pause/stop function on the speechControl. The only reason I created all of them was so each of them could have their own images and so they can play different animations before changing state, but I didn't get to that yet.
// IMSpeechControlPlay.h
#import "IMSpeechControlButton.h"
#interface IMSpeechControlPlay : IMSpeechControlButton {
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
#end
// IMSpeechControlPlay.m
#import "IMSpeechControlPlay.h"
#implementation IMSpeechControlPlay
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
// TODO: set the image view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestureRecognizer.numberOfTapsRequired = 1;
gestureRecognizer.numberOfTouchesRequired = 1;
gestureRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[speechControl play];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
I found the problem, it was in this line:
IMSpeechControlButton *currentView = (IMSpeechControlButton *)[[self.view subviews] objectAtIndex:0]; in the changeState method.
I'd changed and moved this line several times during development, and had an older version of the file where it was correctly stated as:
UIView *currentView = [[self.view subviews] objectAtIndex:0];
but I just noticed that the file I was building has the first version. The version that I copied the source from turned out to be an old version that looked like this:
UIView *currentView = (IMSpeechControl *)[[self.view subviews] objectAtIndex:0];
Changing that to a UIView pointer makes it work. Looking at the debug info it looks like I was wrong about the subviews not getting retained, they were actually just unavailable in the debugger being cast away when the play control was pressed. Setting the speechControl variable before calling init actually works fine.
Thanks for all the advice and so quickly.
This method is just really wrong
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
Basically there is nothing to return here
self.speechControl = control;
return [self initWithFrame:aRect];
Because you haven't actually inited "self" yet. You need to rewrite that function to have a proper initializer like the one you have in IMSpeechControlPlay.

How to resize a UISwitch?

I have made a custom UISwitch (from this post). But the problem is, my custom texts are a bit long. Is there any way to resize the switch? [I tried setBounds, did not work]
Edit:
Here is the code I used:
#interface CustomUISwitch : UISwitch
- (void) setLeftLabelText: (NSString *) labelText;
- (void) setRightLabelText: (NSString *) labelText;
#end
#implementation CustomUISwitch
- (UIView *) slider
{
return [[self subviews] lastObject];
}
- (UIView *) textHolder
{
return [[[self slider] subviews] objectAtIndex:2];
}
- (UILabel *) leftLabel
{
return [[[self textHolder] subviews] objectAtIndex:0];
}
- (UILabel *) rightLabel
{
return [[[self textHolder] subviews] objectAtIndex:1];
}
- (void) setLeftLabelText: (NSString *) labelText
{
[[self leftLabel] setText:labelText];
}
- (void) setRightLabelText: (NSString *) labelText
{
[[self rightLabel] setText:labelText];
}
#end
mySwitch = [[CustomUISwitch alloc] initWithFrame:CGRectZero];
//Tried these, but did not work
//CGRect aFrame = mySwitch.frame;
//aFrame.size.width = 200;
//aFrame.size.height = 100;
//mySwitch.frame = aFrame;
[mySwitch setLeftLabelText: #"longValue1"];
[mySwitch setRightLabelText: #"longValue2"];
The simplest way is to resize it, as a view:
UISwitch *mySwitch = [[UISwitch alloc] init];
mySwitch.transform = CGAffineTransformMakeScale(0.75, 0.75);
and you don't have to care about anything else!
Here is the swift 3 version of mxg answer:
let mySwitch = UISwitch()
mySwitch.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
Many controls are meant to be a specific size. If you were implementing this yourself, you would override setFrame:, adjust the frame parameter to match your control's required size, and then pass that to [super setFrame:].
If you subclass a control that has this behavior, there's really no way to override it because your subclass will inherit the superclass's implementation of setFrame:, which modifies your frame rectangle. And there's no way to set the frame of your control without calling [super setFrame:].
You'll most likely have to inherit from UIControl and implement the properties/behaviors you want from UISwitch manually to work around this.
UISwitch is not designed to be customized.
I think the your best solution is to download one of the custom switch implementations mentioned in the other question that you referred to. Either UICustomSwitch or RCSwitch. They both should be able to handle wide labels.
There is no option for resizing uiswitch in xib, so need to create and resize it in controller class,
UISwitch *onoff = [[UISwitch alloc] initWithFrame: CGRectMake(0, 0, 10, 10)];
onoff.transform = CGAffineTransformMakeScale(0.50, 0.50);
[self.view addSubview:onoff];
If you want to resize switch put through the Storyboard or nib, You can subclass UISwitch and override awakeFromNib method:
- (void)awakeFromNib {
self.transform = CGAffineTransformMakeScale(0.75, 0.75);
}
Select the switch control and change it's class to your custom switch class.
Here is a solution in code:
UISwitch *mySwitchNewsletter = [[UISwitch alloc] initWithFrame: CGRectMake(varSettingsSwitchNewsletter_x,
varSettingsSwitchNewsletter_y,
varSettingsSwitchNewsletter_width,
varSettingsSwitchNewsletter_height)];
if (mySwitchNewsletter != nil) {
[varCommerceSettingsView addSubview:mySwitchNewsletter];
float mySwitchScaleFactor = (varSettingsSwitchNewsletter_scale / 100.0);
CGFloat dX=mySwitchNewsletter.bounds.size.width/2, dY=mySwitchNewsletter.bounds.size.height/2;
mySwitchNewsletter.transform = CGAffineTransformTranslate(CGAffineTransformScale(CGAffineTransformMakeTranslation(-dX, -dY), mySwitchScaleFactor, mySwitchScaleFactor), dX, dY);
mySwitchNewsletter release];
}
Where varSettingsSwitchNewsletter_scale is an int from 0 to 100 (%).
// Just in case someone trying to hard code UISwitch in Xcode 6.4 the following is working
// in .h
#property UISwitch * onoff;
// in .m
self.onoff = [[UISwitch alloc] initWithFrame:CGRectMake(160, 40, 0, 0)];
_onoff.transform = CGAffineTransformMakeScale(0.50, 0.50);
[self.view addSubview:self.onoff];

How can I loop through all subviews of a UIView, and their subviews and their subviews

How can I loop through all subviews of a UIView, and their subviews and their subviews?
Use recursion:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
NSLog(#"%#", self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy];
}
}
#end
// In your implementation
[myView logViewHierarchy];
Well here is my solution using recursion and a wrapper(category/extension) for the UIView class.
// UIView+viewRecursion.h
#interface UIView (viewRecursion)
- (NSMutableArray*) allSubViews;
#end
// UIView+viewRecursion.m
#implementation UIView (viewRecursion)
- (NSMutableArray*)allSubViews
{
NSMutableArray *arr=[[[NSMutableArray alloc] init] autorelease];
[arr addObject:self];
for (UIView *subview in self.subviews)
{
[arr addObjectsFromArray:(NSArray*)[subview allSubViews]];
}
return arr;
}
#end
Usage : Now you should be looping through all the sub views and manipulate them as needed.
//disable all text fields
for(UIView *v in [self.view allSubViews])
{
if([v isKindOfClass:[UITextField class]])
{
((UITextField*)v).enabled=NO;
}
}
Here's another Swift implementation:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
A solution in Swift 3 that gives all subviews without including the view itself:
extension UIView {
var allSubViews : [UIView] {
var array = [self.subviews].flatMap {$0}
array.forEach { array.append(contentsOf: $0.allSubViews) }
return array
}
}
I tag everything when it's created. Then it's easy to find any subview.
view = [aView viewWithTag:tag];
Just found an interesting way to do this through the debugger:
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
references this Apple Technote:
https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
Just make sure your debugger is paused (either set a break point of pause it manually) and you can ask for the recursiveDescription.
Here is an example with actual view looping and breaking functionality.
Swift:
extension UIView {
func loopViewHierarchy(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
var stop = false
block(self, &stop)
if !stop {
self.subviews.forEach { $0.loopViewHierarchy(block: block) }
}
}
}
Call example:
mainView.loopViewHierarchy { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Reversed looping:
extension UIView {
func loopViewHierarchyReversed(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
for i in stride(from: self.highestViewLevel(view: self), through: 1, by: -1) {
let stop = self.loopView(view: self, level: i, block: block)
if stop {
break
}
}
}
private func loopView(view: UIView, level: Int, block: (_ view: UIView, _ stop: inout Bool) -> ()) -> Bool {
if level == 1 {
var stop = false
block(view, &stop)
return stop
} else if level > 1 {
for subview in view.subviews.reversed() {
let stop = self.loopView(view: subview, level: level - 1, block: block)
if stop {
return stop
}
}
}
return false
}
private func highestViewLevel(view: UIView) -> Int {
var highestLevelForView = 0
for subview in view.subviews.reversed() {
let highestLevelForSubview = self.highestViewLevel(view: subview)
highestLevelForView = max(highestLevelForView, highestLevelForSubview)
}
return highestLevelForView + 1
}
}
Call example:
mainView.loopViewHierarchyReversed { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Objective-C:
typedef void(^ViewBlock)(UIView* view, BOOL* stop);
#interface UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block;
#end
#implementation UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block {
BOOL stop = NO;
if (block) {
block(self, &stop);
}
if (!stop) {
for (UIView* subview in self.subviews) {
[subview loopViewHierarchy:block];
}
}
}
#end
Call example:
[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
if ([view isKindOfClass:[UIButton class]]) {
/// use the view
*stop = YES;
}
}];
With the help of Ole Begemann. I added a few lines to incorporate the block concept into it.
UIView+HierarchyLogging.h
typedef void (^ViewActionBlock_t)(UIView *);
#interface UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction;
#end
UIView+HierarchyLogging.m
#implementation UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction {
//view action block - freedom to the caller
viewAction(self);
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:viewAction];
}
}
#end
Using the HierarchyLogging category in your ViewController. You are now have freedom to what you need to do.
void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(#"%#", view);
}
};
[self.view logViewHierarchy: ViewActionBlock];
Need not create any new function. Just do it when debugging with Xcode.
Set a breakpoint in a view controller, and make the app pause at this breakpoint.
Right click the empty area and press "Add Expression..." in Xcode's Watch window.
Input this line:
(NSString*)[self->_view recursiveDescription]
If the value is too long, right click it and choose "Print Description of ...". You will see all subviews of self.view in the console window. Change self->_view to something else if you don't want to see subviews of self.view.
Done! No gdb!
Here is a recursive code:-
for (UIView *subViews in yourView.subviews) {
[self removSubviews:subViews];
}
-(void)removSubviews:(UIView *)subView
{
if (subView.subviews.count>0) {
for (UIView *subViews in subView.subviews) {
[self removSubviews:subViews];
}
}
else
{
NSLog(#"%i",subView.subviews.count);
[subView removeFromSuperview];
}
}
By the way, I made an open source project to help with this sort of task. It's really easy, and uses Objective-C 2.0 blocks to execute code on all views in a hierarchy.
https://github.com/egold/UIViewRecursion
Example:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
Here is a variation on Ole Begemann's answer above, which adds indentation to illustrate the hierarchy:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces {
if (whiteSpaces == nil) {
whiteSpaces = [NSString string];
}
NSLog(#"%#%#", whiteSpaces, self);
NSString *adjustedWhiteSpaces = [whiteSpaces stringByAppendingFormat:#" "];
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:adjustedWhiteSpaces];
}
}
#end
The code posted in this answer traverses all windows and all views and all of their subviews. It was used to dump a printout of the view hierarchy to NSLog but you can use it as a basis for any traversal of the view hierarchy. It uses a recursive C function to traverse the view tree.
I wrote a category some time back to debug some views.
IIRC, the posted code is the one that worked. If not, it will point you in the right direction. Use at own risk, etc.
This displays the hierarchy level as well
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
NSLog(#"%d - %#", level, self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy:(level+1)];
}
}
#end
Wish I'd found this page first, but if (for some reason) you want to do this non-recursively, not in a Category, and with more lines of code
I think all of the answers using recursion (except for the debugger option) used categories. If you don't need/want a category, you can just use a instance method. For instance, if you need to get an array of all labels in your view hierarchy, you could do this.
#interface MyViewController ()
#property (nonatomic, retain) NSMutableArray* labelsArray;
#end
#implementation MyViewController
- (void)recursiveFindLabelsInView:(UIView*)inView
{
for (UIView *view in inView.subviews)
{
if([view isKindOfClass:[UILabel class]])
[self.labelsArray addObject: view];
else
[self recursiveFindLabelsInView:view];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.labelsArray = [[NSMutableArray alloc] init];
[self recursiveFindLabelsInView:self.view];
for (UILabel *lbl in self.labelsArray)
{
//Do something with labels
}
}
The method below creates one or more mutable arrays, then loops through the subviews of the input view. In doing so it adds the initial subview then queries as to whether there are any subviews of that subview. If true, it calls itself again. It does so until the all the views of the hierarchy have been added.
-(NSArray *)allSubs:(UIView *)view {
NSMutableArray * ma = [NSMutableArray new];
for (UIView * sub in view.subviews){
[ma addObject:sub];
if (sub.subviews){
[ma addObjectsFromArray:[self allSubs:sub]];
}
}
return ma;
}
Call using:
NSArray * subviews = [self allSubs:someView];