본문 바로가기

학습 노트/iOS (2021)

020 ~ 025. Slider, Segment Control, Switch and Stepper

Slider


Default Slider

Slider는 보통 0에서 1 사이의 값을 가지며, 기본 값은 그 중간인 0.5이다.

따라서 씬에 나타나는 Slider의 컨트롤러인 'thumb'는 중간에 위치한다.
slider는 thumb을 움직여 값을 조절할 수 있고, 조절할 때마다 slider의 value 속성이 변화한다.

value는 왼쪽으로 갈수록 작아지고, 오른쪽으로 갈수록 커지며, 양 끝에 도달하면 더 이상 진행하지 않는다.

thumb이 이동할 때마다 valuChanged 이벤트가 전달되고 이는 target-action으로 처리한다.

목표는 각 slider를 통해 RGB값을 변경하고,
해당 RGB값으로 rootView의 backGroundColor를 설정한다.

//
//  SliderViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SliderViewController: UIViewController {
    @IBOutlet weak var sliderR: UISlider!
    @IBOutlet weak var sliderG: UISlider!
    @IBOutlet weak var sliderB: UISlider!
    
    @IBAction func sliderFunc(_ sender: Any) {
    }
    

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

slider를 각각의 outlet으로 연결하고,
slider들의 이벤트를 처리하기 위해 동일한 action으로 연결한다.

@IBAction func sliderFunc(_ sender: Any) {
	
	let currentVlaueR = sliderR.value
	let currentValueG = sliderG.value
	let currentValueB = sliderB.value
}

slider의 현재 값에 접근하기 위해선 각 슬라이더의 value 속성에 접근해야 한다.

Slider의 Value 속성은 Float의 형식을 갖는다.
RGB 값을 변경하기 위해서는 해당 값을 CGFloat으로 변경해 줄 필요가 있다.

@IBAction func sliderFunc(_ sender: Any) {
	
	let currentVlaueR = CGFloat(sliderR.value)
	let currentValueG = CGFloat(sliderG.value)
	let currentValueB = CGFloat(sliderB.value)
}

형식을 CGFloat으로 변경한다.

@IBAction func sliderFunc(_ sender: Any) {
	
	let currentVlaueR = CGFloat(sliderR.value)
	let currentValueG = CGFloat(sliderG.value)
	let currentValueB = CGFloat(sliderB.value)
	
	let NewColor = UIColor(red: currentVlaueR, green: currentValueG, blue: currentValueB, alpha: 1.0)
	
	view.backgroundColor = NewColor
}

이후 해당 값을 사용해 새 RGB 값으로 혼합하고,
rootView의 BackgroundColor로 설정한다.

Slider의 기본값은 inspector에서 설정해도 되지만,

override func viewDidLoad() {
	super.viewDidLoad()

	sliderR.value = 1
	sliderG.value = 1
	sliderB.value = 1
}

위와 같이 코드를 통해 뷰가 로드됨과 동시에 value값을 조절하는 방법도 가능하다.


결과

 


위와 같이 Slider의 조작에 따라 즉각적으로 rootView의 색이 바뀌는 것을 확인할 수 있다.

override func viewDidLoad() {
	super.viewDidLoad()
	
//        sliderR.value = 1
//        sliderG.value = 1
//        sliderB.value = 1

	sliderR.setValue(1, animated: true)
	sliderG.setValue(1, animated: true)
	sliderB.setValue(1, animated: true)
	
	sliderR.maximumValue = 10
	sliderG.minimumValue = -10
}

외에도 setValue() 메소드를 사용해 기본값과 애니메이션을 적용할 수도 있고,
maximumValue와 minimumValue 속성에 접근하여 최댓값과 최솟값을 변경할 수도 있다.

 

Custom Slider

Slider는 가운데에 존재하는 둥근 원 모양의 컨트롤러인 Thumb과
Thumb을 기준으로 왼쪽을 Minimum Track, 오른쪽을 Maximum Track이라고 부른다.

Slider는 이 트랙이나 thumb의 색이나 이미지를 변경할 수 있는데,
둘을 동시에 변경하는 것은 불가능하다.

slider의 attribute inspector의 Min Image와 Max Image는 슬라이더의 양 끝에 이미지를 추가하고,
Min Track, Max Track, Thumb Tint는 각각의 색상을 변경한다.
'Thumb의 이미지는 inspector에서 지원하지 않고, 코드에서 구현해야 한다.'

//
//  CustomSliderViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class CustomSliderViewController: UIViewController {
    @IBOutlet weak var slider: UISlider!
    

    override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(systemName: "lightbulb")
        
        slider.minimumTrackTintColor = UIColor.green
        slider.maximumTrackTintColor = UIColor.red
        
        slider.setThumbImage(image, for: .normal)
    }
}

thumb 이미지 설정은 setThumbImage 메소드로 진행한다.
slider도 다른 Control들과 마찬가지로 상태를 가지며, 변경할 이미지를 포함해 설정할 상태까지 전당 해야 한다.
Track의 색상은 minimumTrckTintColor와 maximumTrackTintColor로 설정한다.


결과


//
//  CustomSliderViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class CustomSliderViewController: UIViewController {
    @IBOutlet weak var slider: UISlider!
    

    override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(systemName: "lightbulb")
        
        slider.minimumTrackTintColor = UIColor.green
        slider.maximumTrackTintColor = UIColor.red
        
//        slider.setThumbImage(image, for: .normal)
        
        slider.thumbTintColor = UIColor.black
    }
}

thumb의 색을 변경하고자 한다면 thumTintColor 속성을 변경한다.


결과

 


Non Continuous Slider

slider는 기본적으로 thumb을 움직이는 동안 반복적으로 valueChanged 이벤트를 전달한다.
하지만 Attribute inspector의 Continuous Update를 해제하면 thumb을 드래그하는 동안은 이벤트를 전달하지 않게 된다.

간단하게 위에서 작성했던 RGB슬라이더에서 설정을 변경할 수 있다.


결과

 


다른 slider와는 다르게 드래그를 마친 뒤 손을 뗐을 때 값이 변경되는 것을 확인할 수 있다.

slider.isContinuous = false

해당 설정은 위와 같이 설정값을 변경할 수 있다.

 

Segemented Control


두 개 이상의 버튼을 하나의 그룹으로 묶어 놓은 것으로,
주로 연관된 옵션 중 하나를 선택해야 할 때 사용한다.

목표는 segment를 사용해서 label의 정렬을 변경하도록 한다.

  • style
    사용하지 않는 속성이다.
  • Segments
    세그먼트의 수를 결정한다.
    해당 속성은 2 이상의 값으로만 설정할 수 있다.
  • Segment
    각각의 세그먼트를 설정한다.
    설정할 세그먼트를 선택하고 설정을 진행한다.
  • Title, Image
    세그먼트에 표시될 내용을 설정한다.
    이 둘은 동시에 존재할 수 없다.
    Selected 속성은 개별 세그먼트의 상태를 설정한다.
    세그먼트 전체의 상태는

    Control의 State에서 설정하니 혼동하지 않도록 주의한다.

각각의 세그먼트를 위와 같이 설정하고 코드에서 기능을 구현한다.

//
//  SegmentViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SegmentViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var segment: UISegmentedControl!
    

    override func viewDidLoad() {
        super.viewDidLoad()

    }

}

Label과 Segment를 outlet으로 연결한다.

 

Label과 SegmentControl 동기화 하기.

//
//  SegmentViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SegmentViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var segment: UISegmentedControl!
    

    override func viewDidLoad() {
        super.viewDidLoad()
        segment.selectedSegmentIndex = label.textAlignment.rawValue

    }

}

뷰가 처음 표시될 때 현재 Label의 정렬 상태로 segment를 초기화하도록 한다.

@IBAction func segmentFunc(_ sender: UISegmentedControl) {
	label.textAlignment = NSTextAlignment(rawValue: sender.selectedSegmentIndex) ?? .center
}

이후 segment를 선택하면 동작할 수 있도록 action으로 연결한다.
이때 선택된 segment를 전달할 수 있도록 sender의 형식은 UISegmentControl로 변경한다.
label의 textAlignment를 segment의 인덱스로 업데이트한다.
단, textAlignment의 형식은 NSTextAlignment이므로 형 변환하여 전달할 수 있도록 한다.


결과

 


segment는 선택사항을 계속 유지하는 것이 기본적인 동작이다.
단 State 속성을 변경하면 이벤트는 발생하지만 반영을 유지하진 않는다.

//
//  SegmentViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SegmentViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var segment: UISegmentedControl!
    @IBOutlet weak var switchBtn: UISwitch!
    @IBAction func switchFunc(_ sender: UISwitch) {
        
    }
    
    
    
    @IBAction func segmentFunc(_ sender: UISegmentedControl) {
        label.textAlignment = NSTextAlignment(rawValue: sender.selectedSegmentIndex) ?? .center
    }
    
    

    override func viewDidLoad() {
        super.viewDidLoad()
        segment.selectedSegmentIndex = label.textAlignment.rawValue

    }

}

Switch를 하나 추가하고 코드에 outlet과 action으로 연결한다.

override func viewDidLoad() {
	super.viewDidLoad()
	segment.selectedSegmentIndex = label.textAlignment.rawValue
	switchBtn.isOn = segment.isMomentary
	
}

이후 view가 로드되면 Switch를 초기화해 준다.

@IBAction func switchFunc(_ sender: UISwitch) {
	segment.isMomentary = sender.isOn
}

Switch와 isMomentary 속성을 동기화해 준다.


결과

 


이전과는 다르게 Label의 정렬은 변하지만 Segment의 선택이 유지되지 않은 것을 확인할 수 있다.

 

코드에서 SegmnetControl 속성 변경하기

override func viewDidLoad() {
	super.viewDidLoad()
	segment.selectedSegmentIndex = label.textAlignment.rawValue
	switchBtn.isOn = segment.isMomentary
	
	segment.setTitle("왼쪽으로 정렬", forSegmentAt: 0)
}

ViewDidLoad에서 Segment의 setTitle 메소드를 사용해 segment의 title을 변경할 수 있다.
첫 번째 메소드로 변경할 문장, 두 번째 메소드로 변경할 segment를 지정한다.


결과

 


첫 번째 segment의 title이 '왼쪽으로 정렬'로 변경됐다

 

SegmentControl 실시간 반영하기

segment를 동적으로 설정하기 위해 위와 같이 화면을 구성하고

//
//  CustomSegmentViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class CustomSegmentViewController: UIViewController {
    
    @IBOutlet weak var segment: UISegmentedControl!
    @IBOutlet weak var textfield: UITextField!
    
    @IBAction func removeBtn(_ sender: Any) {
    }
    @IBAction func insertBtn(_ sender: Any) {
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

segment와 textfield는 outlet으로,
button은 action으로 코드에 연결한다.

@IBAction func insertBtn(_ sender: Any) {
	guard let title = textfield.text, title.count > 0 else {
		return
	}
	segment.insertSegment(withTitle: title, at: segment.numberOfSegments, animated: true)
    textfield.text = nil
}

textField에 내용이 존재하는 경우 새 segment를 삽입한다.
이 때는 insertSegment 메소드를 사용하는데,

두 가지의 메소드를 제공한다.
image를 파라미터로 하는 insertSegment는 imageSegment를 추가한다.
title을 파라미터로 하는 inserSegment는 textSegment를 추가한다.

실습에선 텍스트를 입력받아 segment를 추가하므로 textSegment를 추가해야 한다.

첫 번째 파라미터로는 title을, 두 번째 파라미터로는 삽입할 index를, 세 번째 파라미터로는 애니메이션 여부를 결정한다.
가장 뒤에 추가하길 원하므로, numberOfSegments를 통해 현재 존재하는 segement의 수를 전달한다.

segment를 추가한 뒤에는 textField에 nil을 전달해 다시 공란으로 초기화한다.


결과

 


@IBAction func removeBtn(_ sender: Any) {
	guard let title = textfield.text, title.count > 0 else {
		return
	}
	for index in 0...segment.numberOfSegments {
		if let currentTitle = segment.titleForSegment(at: index), title == currentTitle {
			segment.removeSegment(at: index, animated: true)
            break
		}
	}
    textfield.text = nil
}

removeSegment는 segment를 삭제할 수 있지만, title을 기준으로 삭제하는 것이 아닌 index를 기준으로 삭제한다.
따라서 title을 기준으로 segment를 검색해서 일치하는 index를 찾아내야 삭제할 수 있다.

segment의 title은 titleForSegment메소드를 이용해 index를 전달하면 접근할 수 있다.


결과

 


title이 일치하는 segment를 찾아 삭제하는 것을 확인할 수 있다.

 

segment의 디자인 바꾸기

UISegmentedControl에서 제공하는 속성을 사용해 SegmentedControl의 디자인을 바꿀 수 있다.

override func viewDidLoad() {
	super.viewDidLoad()
	
	let normalIamge = UIImage(named: "slot-1")
	let selectImage = UIImage(named: "slot-2")
	
	segment.setBackgroundImage(normalIamge, for: .normal, barMetrics: .default)
	segment.setBackgroundImage(selectImage, for: .selected, barMetrics: .default)
}

사용할 이미지를 상수에 저장한 다음 setBackgroundImage메소드로 backgroundImage를 변경한다.

첫 번째 파라미터는 사용할 이미지, 두 번째 파라미터는 적용할 상태이다.
backgroundImage는 기본적으로 처음과 끝 두 가지로만 구성된다.


결과


따라서 존재하지 않는 중간의 segment는 별도의 이미지가 존재하지 않는데,
세그먼트가 동적으로 구성되거나, 수가 많아지는 경우 이를 모두 저장하는 것은 비효율적이다.
따라서 존재하지 않는 이미지를 길게 늘여 이어 붙이는데 오른쪽 사진과 같이 된다.
지금은 복잡한 이미지를 사용해서 부자연스럽지만 단순한 형태의 바(bar) 모양 디자인이라면 자연스러워진다.

위와 같이 segment들을 구분하는 선을 divider라고 부른다.
지금도 적당히 작동하지만 divider를 변경해 디자인의 완성도를 높일 수도 있다.

segment.setDividerImage(dividerImage: UIImage?, forLeftSegmentState: UIControl.State, rightSegmentState: UIControl.State, barMetrics: UIBarMetrics)

setDividerImage메소드를 사용하며
첫 번째 메소드는 사용할 이미지를, 두 번째 메소드는 왼쪽 segment의 상태, 세 번째 메소드는 오른쪽 segment의 상태,
네 번째 메소드는 default를 전달한다.

override func viewDidLoad() {
	super.viewDidLoad()
	
	let normalIamge = UIImage(named: "segment_normal")
	let selectImage = UIImage(named: "segment_selected@2s")
	
	segment.setBackgroundImage(normalIamge, for: .normal, barMetrics: .default)
	segment.setBackgroundImage(selectImage, for: .selected, barMetrics: .default)
	
	segment.setDividerImage(UIImage(named: "segment_normal_normal"), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_selected_normal"), forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_normal_selected"), forLeftSegmentState: .normal, rightSegmentState: .selected, barMetrics: .default)
}

실습을 위해 강의 파일을 사용해 진행한다.

적용했더니 비율이 묘해진 것을 볼 수 있다.
이는 segment의 정렬이 divider의 영역을 감안하지 않기 때문인데

debugView를 보면 divider가 양쪽 segment의 영역을 침범하고 있는 것을 확인할 수 있다.
이를 수정하기 위해 보정이 필요하다.

override func viewDidLoad() {
        super.viewDidLoad()
        
        let normalIamge = UIImage(named: "segment_normal")
        let selectImage = UIImage(named: "segment_selected@2s")
        
        segment.setBackgroundImage(normalIamge, for: .normal, barMetrics: .default)
        segment.setBackgroundImage(selectImage, for: .selected, barMetrics: .default)
        
        segment.setDividerImage(UIImage(named: "segment_normal_normal"), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
        segment.setDividerImage(UIImage(named: "segment_selected_normal"), forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
        segment.setDividerImage(UIImage(named: "segment_normal_selected"), forLeftSegmentState: .normal, rightSegmentState: .selected, barMetrics: .default)
        
        let width = (UIImage(named: "segment_normal_normal")!.size.width - 20) / 3.0
        var offset = UIOffset(horizontal: width, vertical: 0)
        segment.setContentPositionAdjustment(offset, forSegmentType: .left, barMetrics: .default)
    }

width로 divider에서 보정할 만큼의 너비를 구하고,
이를 좌표화 해서 offset에 저장한다.
이후 위치를 조정하는 setContentPositionAdjustment 메소드를 사용해 위치를 조정한다.

첫 번째 메소드는 offset을 전달하고, 두 번째 메소드는 대상을 선택한다. 전달한 left는 좌측의 segment에 해당한다.

오른쪽에 해당하는 segment는 왼쪽과는 반대 방향으로 이동해야 한다.

override func viewDidLoad() {
	super.viewDidLoad()
	
	let normalIamge = UIImage(named: "segment_normal")
	let selectImage = UIImage(named: "segment_selected@2s")
	
	segment.setBackgroundImage(normalIamge, for: .normal, barMetrics: .default)
	segment.setBackgroundImage(selectImage, for: .selected, barMetrics: .default)
	
	segment.setDividerImage(UIImage(named: "segment_normal_normal"), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_selected_normal"), forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_normal_selected"), forLeftSegmentState: .normal, rightSegmentState: .selected, barMetrics: .default)
	
	let width = (UIImage(named: "segment_normal_normal")!.size.width - 20) / 3.0
	var offset = UIOffset(horizontal: width, vertical: 0)
	segment.setContentPositionAdjustment(offset, forSegmentType: .left, barMetrics: .default)
	
	offset = UIOffset(horizontal: -width, vertical: 0)
	segment.setContentPositionAdjustment(offset, forSegmentType: .right, barMetrics: .default)
}

위와 같이 width값을 반전시켜 위치를 조정한다.


결과

 


이제는 segment의 title이 버튼의 가운데에 위치하고 있으나,
전환 과정이 어색하다.
이는 normal과 selected에만 이미지를 적용했기 때문인데, highlighted 상태의 적용이 빠져 normal 상태의 background를 어둡게 처리해 사용하고, divider 설정이 빠져 전환 과정이 어색해지는 것이다.

override func viewDidLoad() {
	super.viewDidLoad()
	
	let normalIamge = UIImage(named: "segment_normal")
	let selectImage = UIImage(named: "segment_selected@2s")
	
	segment.setBackgroundImage(normalIamge, for: .normal, barMetrics: .default)
	segment.setBackgroundImage(normalIamge, for: .highlighted, barMetrics: .default)
	segment.setBackgroundImage(selectImage, for: .selected, barMetrics: .default)
	
	segment.setDividerImage(UIImage(named: "segment_normal_normal"), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_selected_normal"), forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_normal_selected"), forLeftSegmentState: .normal, rightSegmentState: .selected, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_normal_selected"), forLeftSegmentState: .highlighted, rightSegmentState: .selected, barMetrics: .default)
	segment.setDividerImage(UIImage(named: "segment_selected_normal"), forLeftSegmentState: .selected, rightSegmentState: .highlighted, barMetrics: .default)
	
	let width = (UIImage(named: "segment_normal_normal")!.size.width - 20) / 3.0
	var offset = UIOffset(horizontal: width, vertical: 0)
	segment.setContentPositionAdjustment(offset, forSegmentType: .left, barMetrics: .default)
	
	offset = UIOffset(horizontal: -width, vertical: 0)
	segment.setContentPositionAdjustment(offset, forSegmentType: .right, barMetrics: .default)
}

이렇게 highlighted 상태에서도 normal이미지를 그대로 사용하도록 설정하고, divider도 수정해 주면


결과


전환이 자연스럽게 된다.

이렇게 segmentControl을 새로 디자인하는 것은 inspector에서 지원하지 않고 코드에서 진행해야만 한다.
생각보다 귀찮고 고려해야 할 게 많기 때문에 위와 같은 UI는 segmentControl이 아닌 Button으로 구성하는 것이 더 효율적이다.

 

switch


switch는 비교적 단순하고 제한적인 control이다.

Control, View는 상속된 속성들이고,
Switch 자체 속성의 title과 style은 mac catalyst를 위한 속성이지 앱 개발 시엔 사용되지 않는 속성들이다.
이들을 제외하면 state와 On Tint, Thumb Tint가 전부다.

  • State
    switch의 on/off 상태를 변경한다.
  • On Tint
    On 상태의 강조 색을 변경한다.
  • Thumb Tint
    Thumb의 색을 변경한다.

결과

 


Switch와 imageView 동기화 하기.

switch는 TouchUpInside가 아닌 Value Changed 이벤트를 발생시킨다.
isOn속성의 값을 바꿔 switch 상태를 나타낸다.

//
//  SwitchViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SwitchViewController: UIViewController {
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var switchStat: UISwitch!
    
    @IBAction func switchFunc(_ sender: UISwitch) {
    }
    
    

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

전구를 표시하고 있는 imageView는 outlet으로,
switch는 isOn 속성에 접근할 수 있도록 outlet과 이벤트에 대응할 수 있도록 action으로 연결했다.

//
//  SwitchViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SwitchViewController: UIViewController {
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var switchStat: UISwitch!
    
    @IBAction func switchFunc(_ sender: UISwitch) {
        image.isHighlighted = sender.isOn
    }
    
    

    override func viewDidLoad() {
        super.viewDidLoad()
        
        switchStat.isOn = image.isHighlighted
    }
}

viewDidLoad에서 view가 로드되면 switch의 상태를 초기화하도록 하고,
imageView의 highlight 속성을 switch의 isOn 속성과 동기화했다.


결과


그러면 이렇게 Switch를 통해 전구 이미지를 켜고 끌 수 있다.                                                                                                                                                                                                                                                                                                                                                                                                                                

 

Button과 Switch와 imageView 동기화 하기.

이번엔 버튼을 하나 생성하고

//
//  SwitchViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SwitchViewController: UIViewController {
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var switchStat: UISwitch!
    
    @IBAction func switchFunc(_ sender: UISwitch) {
        image.isHighlighted = sender.isOn
    }
    
    @IBAction func button(_ sender: Any) {
        switchStat.isOn.toggle()
    }
    

    override func viewDidLoad() {
        super.viewDidLoad()
        
        switchStat.isOn = image.isHighlighted
    }
}

해당 버튼을 터치하면 switch이 상태를 바꾸도록 구현했다.


결과

 


이 경우엔 switch를 통해 값이 변경되지 않음으로 이벤트가 발생하지 않아 이미지가 바뀌지 않는다.
또한, switch에 애니메이션이 적용되지 않아 어색하기도 하다.

//
//  SwitchViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class SwitchViewController: UIViewController {
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var switchStat: UISwitch!
    
    @IBAction func switchFunc(_ sender: UISwitch) {
        image.isHighlighted = sender.isOn
    }
    
    @IBAction func button(_ sender: Any) {
        switchStat.setOn(!switchStat.isOn, animated: true)
        switchFunc(switchStat)
    }
    

    override func viewDidLoad() {
        super.viewDidLoad()
        
        switchStat.isOn = image.isHighlighted
    }
}

이번엔 속성을 직접 변경하는 게 아닌 setOn 메소드를 사용했다.

첫 번째 메소드로 상태를 전달하고, 두 번째 메소드로 애니메이션 여부를 결정한다.
첫 번째 메소드에는 현재의 상태를 반전시켜 전달했다.
이후 앞서 작성했던 전구를 켜는 함수를 호출했다.


결과

 


button과 switch가 동일한 동작을 수행하는 것을 확인할 수 있다.

 

Stepper


stepper 두 개의 버튼을 통해 일정 범위의 값을 증가시키거나 감소시키는 기능을 한다.

//
//  StepperViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class StepperViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var stepper: UIStepper!

    @IBOutlet weak var switch1: UISwitch!
    @IBOutlet weak var switch2: UISwitch!
    @IBOutlet weak var switch3: UISwitch!
    
    @IBAction func stepperFunc(_ sender: UIStepper) {
    }
    
    
    @IBAction func switchFunc1(_ sender: UISwitch) {
        
    }
    
    @IBAction func switchFunc2(_ sender: UISwitch) {
        
    }
    
    @IBAction func switchFunc3(_ sender: UISwitch) {
        
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

화면 구성과 코드 연결은 위와 같다.

stepper의 속성은 위와 같다.

  • Value
    현재 값을 나타낸다.
  • Minimum
    최솟값을 설정한다.
  • Maximum
    최댓값을 설정한다.
  • Step
    값을 변경시킬 단위를 설정한다.
  • Autorepeat
    터치를 지속하고 있는 동안 값을 자동으로 변경한다.
  • Continuous
    valueChangeed 이벤트가 전달되는 시점을 지정한다.
    활성화돼 있다면 값이 변경되는 즉시 이벤트를 발생시키고,
    비활성화돼 있다면 터치를 멈출 때 이벤트를 발생시킨다.
  • Wrap
    최댓값이나 최솟값에 도달했을 때 작동방식을 결정한다.
    활성화돼 있다면 값을 순환시킨다.
    비활성화돼 있다면 증가와 감소를 멈춘다.

목표는 stepper를 조작했을 때 값을 label에 표시한다.
switch는 autorepeat, continuous, wrap 속성을 조작한다.

//
//  StepperViewController.swift
//  controltest
//
//  Created by Martin.Q on 2021/08/11.
//

import UIKit

class StepperViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var stepper: UIStepper!

    @IBOutlet weak var switch1: UISwitch!
    @IBOutlet weak var switch2: UISwitch!
    @IBOutlet weak var switch3: UISwitch!
    
    @IBAction func stepperFunc(_ sender: UIStepper) {
        label.text = "\(sender.value)"
    }
    
    
    @IBAction func switchFunc1(_ sender: UISwitch) {
        stepper.autorepeat = sender.isOn
    }
    
    @IBAction func switchFunc2(_ sender: UISwitch) {
        stepper.isContinuous = sender.isOn
    }
    
    @IBAction func switchFunc3(_ sender: UISwitch) {
        stepper.wraps = sender.isOn
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        switch1.isOn = stepper.autorepeat
        switch2.isOn = stepper.isContinuous
        switch3.isOn = stepper.wraps
    }
}

stepper는 값이 변경될 때마다 변경된 값을 label에 반영한다.
각각의 스위치는 autorepeat, isContinuous, wraps 속성에 접근해 상태를 변경한다.
viewDidLoad에서는 모든 스위치를 초기화하도록 했다.


결과


첫 번째와 같이 Autorepeat이 비활성화된다면 버튼을 아무리 길게 눌러도 값은 한 번만 변경된다.
두 번째처럼 Continuous가 비활성화된다면 버튼을 길게 눌러도 값이 연속적으로 변경되지 않는다. 손을 뗀 순간 값이 반영된다.
세 번째 처럼 Wrap이 비활성화 된다면 최댓값과 최솟값에서 더 이상 증가하거나 감소하지 않는다.
활성화한다면 최댓값에서 증가할 경우 최솟값으로, 최솟값에서 감소할 경우 최댓값으로 순환하게 된다.