• Feed
  • Explore
  • Ranking
/
/
    💻 Dev

    Hysteresis Buffer

    Hysteresis Buffer
    지
    지성
    2025.12.16
    ·
    5 min read

    문제 상황

    iOS 앱에서 UI 인터랙션을 구현하다 보면, 값이 경계 근처에서 계속 튀는 현상을 자주 마주하게 된다.

    예를 들어 스크롤 방향에 따라 View를 제어하는 다음과 같은 로직을 생각해 보자.

    • 위로 스크롤 → View 숨김

    • 아래로 스크롤 → View 표시

    이 로직 자체는 단순하지만, 실제 사용 환경에서는 문제가 발생한다. 손가락의 위치가 경계값 근처에 머무르면 View가 보였다가 숨겨졌다가를 반복하며 UI가 불안정해 보이게 된다.

    이럴 때 유용하게 사용할 수 있는 개념이 바로 Hysteresis Buffer다.

    상태를 바꾸기 위한 조건을, 되돌리기 위한 조건보다 더 엄격하게 만드는 것"


    문제 상황: 버퍼 없이 스크롤 방향을 판단하면

    enum ScrollDirection {
        case up
        case down
    }
    
    final class ScrollDirectionDetector {
    
        private var lastOffsetY: CGFloat = 0
    
        func detectDirection(currentOffsetY: CGFloat) -> ScrollDirection {
            defer { lastOffsetY = currentOffsetY }
    
            if currentOffsetY > lastOffsetY {
                return .down
            } else {
                return .up
            }
        }
    }

    이 코드는 논리적으로는 전혀 문제가 없다. 이전 offset과 현재 offset을 비교해 스크롤 방향을 판단한다.

    하지만 실제 스크롤 환경에서는 다음과 같은 요소들 때문에 문제가 발생한다.

    • 손가락의 미세한 움직임

    • 관성 스크롤로 인한 잔 떨림

    • 스크롤의 bounce 구간

    이로 인해 up ↔ down 상태가 프레임 단위로 계속 전환되며, View 역시 짧은 시간 안에 반복적으로 숨겨졌다 나타났다를 반복하게 된다.


    해결 방안: Hysteresis Buffer 도입

    해결 아이디어는 의외로 단순하다.

    • 방향을 바꾸려면 일정 거리 이상 움직여야 한다

    • 그 전까지는 기존 상태를 유지한다

    이를 위해 방향 전환에 필요한 임계값(threshold) 을 두고, 이 값을 넘었을 때만 상태를 변경한다.

    enum ScrollDirection {
        case up
        case down
    }
    
    final class ScrollDirectionHysteresisDetector {
    
        private var lastOffsetY: CGFloat = 0
        private var currentDirection: ScrollDirection = .down
    
        /// 방향 전환에 필요한 최소 이동 거리
        private let hysteresisBuffer: CGFloat = 12
    
        func detectDirection(currentOffsetY: CGFloat) -> ScrollDirection {
            let delta = currentOffsetY - lastOffsetY
    
            switch currentDirection {
            case .down:
                // 위로 충분히 움직였을 때만 방향 전환
                if delta < -hysteresisBuffer {
                    currentDirection = .up
                }
    
            case .up:
                // 아래로 충분히 움직였을 때만 방향 전환
                if delta > hysteresisBuffer {
                    currentDirection = .down
                }
            }
    
            lastOffsetY = currentOffsetY
            return currentDirection
        }
    }
    • 기존 방향을 기준으로 판단한다

    • 반대 방향으로 의미 있는 이동이 발생해야 상태가 전환된다

    • 작은 떨림이나 미세한 움직임에는 반응하지 않는다

    그 결과, UI는 훨씬 안정적으로 동작하게 된다.


    사실 Hysteresis Buffer는 거창한 패턴이라기 보다는, UI를 덜 예민하게 만들어 주는 작은 장치에 가깝다.

    하지만 이 사소한 차이 하나로

    • UI가 훨씬 더 안정적으로 느껴지고,

    • 사용자는 앱이 더 '잘 만들어졌다'고 인식하게 된다

    스크롤, 드래그, 제스처처럼 사용자의 의도가 점진적으로 드러나는 인터랙션이라면, Hysteresis Buffer는 충분히 고려해볼 만한 선택지다.







    - 컬렉션 아티클