Introduction
To switch views with a UISegmentedControl in iOS, add a target action that responds to the .valueChanged event, then show/hide child views (or swap child view controllers) based on the selectedSegmentIndex. The segmented control acts as a tab-like selector — each segment maps to a different content view. The two main approaches are toggling view visibility (simple) and using container view controllers (scalable for complex content).
Basic Setup with View Toggling
1import UIKit
2
3class ViewController: UIViewController {
4 let segmentedControl = UISegmentedControl(items: ["First", "Second", "Third"])
5 let firstView = UIView()
6 let secondView = UIView()
7 let thirdView = UIView()
8
9 override func viewDidLoad() {
10 super.viewDidLoad()
11 setupSegmentedControl()
12 setupContentViews()
13 }
14
15 func setupSegmentedControl() {
16 segmentedControl.selectedSegmentIndex = 0
17 segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
18
19 segmentedControl.translatesAutoresizingMaskIntoConstraints = false
20 view.addSubview(segmentedControl)
21
22 NSLayoutConstraint.activate([
23 segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
24 segmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
25 ])
26 }
27
28 func setupContentViews() {
29 firstView.backgroundColor = .systemRed
30 secondView.backgroundColor = .systemBlue
31 thirdView.backgroundColor = .systemGreen
32
33 for contentView in [firstView, secondView, thirdView] {
34 contentView.translatesAutoresizingMaskIntoConstraints = false
35 view.addSubview(contentView)
36 NSLayoutConstraint.activate([
37 contentView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 16),
38 contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
39 contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
40 contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
41 ])
42 }
43
44 // Show only the first view initially
45 segmentChanged()
46 }
47
48 @objc func segmentChanged() {
49 firstView.isHidden = segmentedControl.selectedSegmentIndex != 0
50 secondView.isHidden = segmentedControl.selectedSegmentIndex != 1
51 thirdView.isHidden = segmentedControl.selectedSegmentIndex != 2
52 }
53}
When the user taps a segment, segmentChanged hides all views except the one matching the selected index.
Container View Controller Approach
For complex content, swap child view controllers instead of simple views:
1class TabContainerViewController: UIViewController {
2 let segmentedControl = UISegmentedControl(items: ["Profile", "Settings", "Activity"])
3 let containerView = UIView()
4
5 private var childVCs: [UIViewController] = []
6 private var currentVC: UIViewController?
7
8 override func viewDidLoad() {
9 super.viewDidLoad()
10
11 childVCs = [ProfileViewController(), SettingsViewController(), ActivityViewController()]
12
13 setupUI()
14 segmentedControl.selectedSegmentIndex = 0
15 segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
16 switchToVC(at: 0)
17 }
18
19 func setupUI() {
20 segmentedControl.translatesAutoresizingMaskIntoConstraints = false
21 containerView.translatesAutoresizingMaskIntoConstraints = false
22 view.addSubview(segmentedControl)
23 view.addSubview(containerView)
24
25 NSLayoutConstraint.activate([
26 segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
27 segmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
28 segmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
29 containerView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8),
30 containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
31 containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
32 containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
33 ])
34 }
35
36 @objc func segmentChanged() {
37 switchToVC(at: segmentedControl.selectedSegmentIndex)
38 }
39
40 func switchToVC(at index: Int) {
41 let newVC = childVCs[index]
42
43 // Remove current child VC
44 currentVC?.willMove(toParent: nil)
45 currentVC?.view.removeFromSuperview()
46 currentVC?.removeFromParent()
47
48 // Add new child VC
49 addChild(newVC)
50 containerView.addSubview(newVC.view)
51 newVC.view.frame = containerView.bounds
52 newVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
53 newVC.didMove(toParent: self)
54
55 currentVC = newVC
56 }
57}
This properly manages the view controller lifecycle — each child VC receives viewWillAppear/viewDidDisappear events.
SwiftUI Implementation
1import SwiftUI
2
3struct ContentView: View {
4 @State private var selectedTab = 0
5
6 var body: some View {
7 VStack {
8 Picker("View", selection: $selectedTab) {
9 Text("First").tag(0)
10 Text("Second").tag(1)
11 Text("Third").tag(2)
12 }
13 .pickerStyle(.segmented)
14 .padding()
15
16 // Switch content based on selection
17 switch selectedTab {
18 case 0:
19 FirstView()
20 case 1:
21 SecondView()
22 default:
23 ThirdView()
24 }
25
26 Spacer()
27 }
28 }
29}
30
31struct FirstView: View {
32 var body: some View {
33 Text("First View Content")
34 .font(.title)
35 .foregroundColor(.red)
36 }
37}
In SwiftUI, Picker with .segmented style replaces UISegmentedControl. The @State variable triggers a view update when the selection changes.
Customizing Appearance
1// UIKit customization
2let control = UISegmentedControl(items: ["One", "Two", "Three"])
3
4// Tint color (entire control)
5control.selectedSegmentTintColor = .systemBlue
6
7// Text attributes for normal state
8control.setTitleTextAttributes([
9 .foregroundColor: UIColor.gray,
10 .font: UIFont.systemFont(ofSize: 14)
11], for: .normal)
12
13// Text attributes for selected state
14control.setTitleTextAttributes([
15 .foregroundColor: UIColor.white,
16 .font: UIFont.boldSystemFont(ofSize: 14)
17], for: .selected)
18
19// Background color
20control.backgroundColor = UIColor.systemGray6
With Navigation Bar
1class NavBarSegmentedVC: UIViewController {
2 override func viewDidLoad() {
3 super.viewDidLoad()
4
5 let segmented = UISegmentedControl(items: ["All", "Favorites", "Recent"])
6 segmented.selectedSegmentIndex = 0
7 segmented.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
8
9 // Place in navigation bar's title view
10 navigationItem.titleView = segmented
11 }
12
13 @objc func segmentChanged(_ sender: UISegmentedControl) {
14 switch sender.selectedSegmentIndex {
15 case 0: showAllItems()
16 case 1: showFavorites()
17 case 2: showRecent()
18 default: break
19 }
20 }
21}
Placing the segmented control in navigationItem.titleView is a common iOS pattern (used in Apple's own apps like Mail and Files).
Common Pitfalls
Not calling didMove(toParent:): When using child view controllers, you must call addChild() before adding the view and didMove(toParent: self) after. Skipping this breaks the view controller lifecycle — viewWillAppear and viewDidAppear will not be called.
Memory from keeping all views loaded: The simple toggle approach loads all views at once. For heavy content (images, web views, maps), this wastes memory. Use the container VC approach and optionally release off-screen VCs.
Missing .valueChanged event: Adding the target with the wrong event (e.g., .touchUpInside) means the action never fires. UISegmentedControl fires .valueChanged, not touch events.
Forgetting selectedSegmentIndex = 0: The default selected index is -1 (none selected). If you do not set an initial selection, no segment appears selected and no content is shown until the user taps.
Animation when switching: Abruptly showing/hiding views feels jarring. Add a UIView.transition or UIView.animate for smooth crossfade: UIView.transition(with: containerView, duration: 0.2, options: .transitionCrossDissolve, animations: { ... }).
Summary
Handle the .valueChanged event on UISegmentedControl to detect selection changes
For simple content, toggle isHidden on child views based on selectedSegmentIndex
For complex content, use container view controllers with proper child VC lifecycle management
In SwiftUI, use Picker with .segmented style bound to a @State variable
Place the segmented control in navigationItem.titleView for navigation-bar integration
Always set selectedSegmentIndex to a valid value in viewDidLoad