Search code examples
iosobjective-cuiscrollviewuitextfield

Adding UITextFields one below another on button click in a UIScrollView


I have a UIScrollView with multiple components.I need to add UITextfields in such a way that on tap of a button the textfields will get add one below another vertically.for eg if button tapped 100 times.100 uitextfields should get added, also there are components below this uitextfield so the uiscrollview should adjust its height accordingly.


Solution

  • Note: Original answer edited with fill / get example code.

    Here is a simple example.

    Add a UIStackView (vertical axis) to the scroll view. With each button tap, create a new UITextField and add it as an arrangedSubview of the stack view.

    The stack view will automatically expand vertically, and its constraints will automatically handle the scroll view's content size.

    • The new fields will be initialized with Placeholder strings.
    • Tapping the "Fill Fields" button will then set the text of each field to a value from a sample data array.
    • Tapping the "Get Text" button will log the text of each field to the debug console.

    ViewController.h

    //
    //  ViewController.h
    //
    //  Created by Don Mag on 12/3/19.
    //
    
    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController
    
    @end
    

    ViewController.m

    //
    //  ViewController.m
    //
    //  Created by Don Mag on 12/3/19.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIButton *theButton;
    @property (strong, nonatomic) UIButton *fillButton;
    @property (strong, nonatomic) UIButton *getButton;
    
    @property (strong, nonatomic) UIScrollView *theScrollView;
    @property (strong, nonatomic) UIStackView *theStackView;
    
    @property (strong, nonatomic) NSMutableArray *theData;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self.view setBackgroundColor:[UIColor whiteColor]];
    
        // instantiate buttons, scrollView and stackViews
    
        // adds a new text field
        _theButton = [UIButton new];
        [_theButton setTranslatesAutoresizingMaskIntoConstraints:NO];
        [_theButton setTitle:@"Add Field" forState:UIControlStateNormal];
        [_theButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];
        [_theButton setBackgroundColor:[UIColor redColor]];
    
        // fills the fields with sample data
        _fillButton = [UIButton new];
        [_fillButton setTranslatesAutoresizingMaskIntoConstraints:NO];
        [_fillButton setTitle:@"Fill Fields" forState:UIControlStateNormal];
        [_fillButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];
        [_fillButton setBackgroundColor:[UIColor redColor]];
    
        // logs the text of each field to debug console
        _getButton = [UIButton new];
        [_getButton setTranslatesAutoresizingMaskIntoConstraints:NO];
        [_getButton setTitle:@"Get Text" forState:UIControlStateNormal];
        [_getButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];
        [_getButton setBackgroundColor:[UIColor redColor]];
    
        // scroll view will hold the stack view filled with fields
        _theScrollView = [UIScrollView new];
        [_theScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [_theScrollView setBackgroundColor:[UIColor cyanColor]];
    
        // stack view to hold the new fields
        _theStackView = [UIStackView new];
        [_theStackView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [_theStackView setAxis:UILayoutConstraintAxisVertical];
        [_theStackView setSpacing:8.0];
    
        // stack view for buttons
        UIStackView *buttonsStackView = [UIStackView new];
        [buttonsStackView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [buttonsStackView setAxis:UILayoutConstraintAxisHorizontal];
        [buttonsStackView setSpacing:8.0];
    
        // add buttons to the buttons stack view
        [buttonsStackView addArrangedSubview:_theButton];
        [buttonsStackView addArrangedSubview:_fillButton];
        [buttonsStackView addArrangedSubview:_getButton];
    
        // add button stack view and scrollView to view
        [self.view addSubview:buttonsStackView];
        [self.view addSubview:_theScrollView];
    
        // add stackView to scrollView
        [_theScrollView addSubview:_theStackView];
    
        UILayoutGuide *g = self.view.safeAreaLayoutGuide;
        UILayoutGuide *sg = _theScrollView.contentLayoutGuide;
    
        [NSLayoutConstraint activateConstraints:@[
    
            // constrain buttons 20-pts from top, centered horizontally
            [buttonsStackView.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
            [buttonsStackView.centerXAnchor constraintEqualToAnchor:g.centerXAnchor constant:0.0],
    
            // constrai scrollView 20-pts below button, 20-pts on each side and bottom
            [_theScrollView.topAnchor constraintEqualToAnchor:_theButton.bottomAnchor constant:20.0],
            [_theScrollView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-20.0],
            [_theScrollView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
            [_theScrollView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
    
            // constrain stackView 8-pts on each side
            [_theStackView.topAnchor constraintEqualToAnchor:sg.topAnchor constant:8.0],
            [_theStackView.bottomAnchor constraintEqualToAnchor:sg.bottomAnchor constant:-8.0],
            [_theStackView.leadingAnchor constraintEqualToAnchor:sg.leadingAnchor constant:8.0],
            [_theStackView.trailingAnchor constraintEqualToAnchor:sg.trailingAnchor constant:-8.0],
    
            // constrain stackView width to scrollView frame width minus 16-pts (for 8-pts on each side)
            [_theStackView.widthAnchor constraintEqualToAnchor:_theScrollView.frameLayoutGuide.widthAnchor constant:-16.0],
    
            ]
        ];
    
        [_theButton addTarget:self action:@selector(addTextField:) forControlEvents:UIControlEventTouchUpInside];
        [_fillButton addTarget:self action:@selector(fillFields:) forControlEvents:UIControlEventTouchUpInside];
        [_getButton addTarget:self action:@selector(getFields:) forControlEvents:UIControlEventTouchUpInside];
    
        // sample data to fill the fields with
        NSString *s = @"This is just random text that will be used to fill the text fields when the fill button is tapped";
        _theData = [s componentsSeparatedByString:@" "].mutableCopy;
    
    }
    
    - (void)fillFields:(id)sender {
        // set the text of each field based on the data array
        NSInteger idx = 0;
        for (UITextField *f in _theStackView.arrangedSubviews) {
            // make sure we don't have more fields than our data array
            if (idx < _theData.count) {
                f.text = _theData[idx++];
            }
        }
    }
    
    - (void)getFields:(id)sender {
        // log the text of each field to debug cosole
        for (UITextField *f in _theStackView.arrangedSubviews) {
            NSLog(@"%@", f.text);
        }
    }
    
    - (void)addTextField:(id)sender {
    
        // instantiate a new text field
        UITextField *f = [UITextField new];
        [f setBorderStyle:UITextBorderStyleRoundedRect];
        [f setBackgroundColor:[UIColor whiteColor]];
    
        // add it to the stack view
        [_theStackView addArrangedSubview:f];
    
        // set its placeholder text (just to make it easy to see what we're doing)
        [f setPlaceholder:[NSString stringWithFormat:@"Text Field %ld", _theStackView.arrangedSubviews.count]];
    
        dispatch_async(dispatch_get_main_queue(), ^{
    
            // auto-scroll to bottom so newly added text field is visible
            CGRect r = CGRectMake(0.0, self.theScrollView.contentSize.height - 1.0, 1.0, 1.0);
            [self.theScrollView scrollRectToVisible:r animated:YES];
    
        });
    
    }
    
    @end
    

    Result after 3 taps:

    enter image description here

    after enough taps to need to scroll:

    enter image description here

    and after tapping the "Fill Fields" button:

    enter image description here