Command Palette

Search for a command to run...

LazyPad는 iPhone을 Mac의 로컬 입력면으로 사용하기 위해 만든 사이드프로젝트입니다.

가까이에 있는 iPhone으로 Mac 커서를 움직이고, 탭으로 클릭하고, 두 손가락으로 스크롤하고, 길게 누른 뒤 움직이면 드래그하는 앱을 목표로 했습니다.

프로젝트를 시작할 때 Swift와 Xcode는 익숙한 도구가 아니었습니다. 그래서 처음부터 기능을 많이 넣기보다, 무엇을 확인해야 다음 단계로 갈 수 있는지 먼저 나누었습니다.

AI는 후보를 빠르게 찾고 작업 단위를 쪼개는 데 사용했습니다. 다만 네트워크 구조, 입력감 알고리즘, 배포 절차는 공식 문서, 논문, 실기기 검증으로 다시 확인했습니다.

이 글은 그 과정을 정리합니다. 핵심은 AI를 활용해 처음 다루는 도구로 앱을 만들되, 중요한 선택은 기본 지식과 검증으로 다시 좁혔다는 점입니다.

처음 나오는 용어를 풀어야 선택 이유가 보입니다

LazyPad는 iPhone 앱, Mac 앱, 로컬 네트워크, 입력 알고리즘, 배포 절차가 함께 움직이는 프로젝트입니다. 그래서 본문에 여러 기술 용어가 나옵니다.

아래 용어를 먼저 풀어두면 이후의 선택 이유를 더 쉽게 따라갈 수 있습니다.

용어쉬운 설명이 글에서 중요한 이유
iPhone touch surfaceiPhone 화면에서 손가락 움직임을 받는 입력 영역입니다LazyPad에서는 이 화면이 Mac의 간접 입력 장치 역할을 합니다
Mac CompanionMac에서 실행되는 별도 앱입니다iPhone이 보낸 입력을 Mac cursor, click, scroll, drag로 실행합니다
Accessibility permissionMac 앱이 사용자 대신 cursor와 click 같은 입력을 실행할 수 있도록 허용하는 권한입니다이 권한이 없으면 Mac Companion이 입력을 실행할 수 없습니다
Local Network permissioniPhone 앱이 같은 Wi-Fi 안의 Mac을 찾고 연결하기 위해 필요한 iOS 권한입니다LazyPad가 cloud가 아니라 가까운 Mac과 직접 연결된다는 경계를 보여줍니다
PairingiPhone과 Mac을 한 번 연결 대상으로 묶는 절차입니다아무 Mac에나 입력을 보내지 않도록 막는 첫 안전 장치입니다
SessionPairing 뒤에 만들어지는 현재 연결 상태입니다연결된 동안 어떤 입력이 유효한지 판단하는 기준이 됩니다
Signed message입력 메시지에 검증 가능한 표시를 붙이는 방식입니다인증되지 않은 입력을 Mac에서 실행하지 않기 위해 필요합니다
Sequence gate메시지 순서를 확인하는 장치입니다오래된 입력이나 순서가 꼬인 입력이 뒤늦게 실행되는 일을 줄입니다
Physical smoke실제 iPhone과 Mac에서 핵심 동작만 빠르게 확인하는 수동 검증입니다자동 테스트만으로는 입력감을 판단하기 어렵기 때문에 필요했습니다
Gate다음 단계로 넘어가기 전에 통과해야 하는 확인 지점입니다입력감, 배포물, 심사 상태를 각각 분리해서 판단하기 위해 사용했습니다

현재 상태는 배포 완료가 아니라 배포 후보 검증입니다

현재 공개적으로 말할 수 있는 범위는 다음과 같습니다.

항목상태
iOS 앱build 0.1.0 (11)을 App Store Connect 외부 TestFlight 그룹에 올리고 Apple Beta App Review에 제출했습니다
입력감 검증build 0.1.0 (10) physical smoke 기준선을 확보했습니다
Mac CompanionDeveloper ID signing, notarization, stapling, Gatekeeper, zip/unzip verification을 통과했습니다
웹 표면lazypad.app landing/support/privacy 초안을 준비했습니다
지원 경로support@lazypad.app 송수신을 확인했습니다

아직 사용하지 않는 표현도 분리했습니다.

사용하지 않는 표현이유
정식 출시 완료아직 출시 상태가 아닙니다
외부 테스트 완료Apple Beta App Review와 tester invite 흐름이 남아 있습니다
Magic Trackpad 완전 대체v0.1 범위를 넘어서는 표현입니다
Bluetooth HID trackpad현재 구조가 아닙니다
모든 macOS gesture 지원구현 범위를 넘어서는 표현입니다

이 구분을 먼저 둔 이유는 현재 상태를 과장하지 않기 위해서입니다. 완료된 검증과 남은 절차를 분리해야 이후의 기술 선택도 정확하게 설명할 수 있습니다.

목표는 기능 데모가 아니라 작동 가능한 입력 앱입니다

처음 목표는 단순했습니다. iPhone 화면을 Mac 입력면처럼 쓰는 것이었습니다.

하지만 입력 앱은 기능 목록만으로 끝나지 않습니다. Mac 커서를 움직이는 순간부터 안전 조건이 필요합니다. Drag가 들어가면 stuck mouse-down을 막아야 하고, 연결이 끊기면 입력 상태를 release해야 합니다. 인증되지 않은 기기에서 입력이 오면 실행하지 않아야 합니다.

그래서 구현 순서를 작게 나누었습니다.

  1. TrackpadKit protocol과 safety test를 먼저 만들었습니다.
  2. Mac Companion에서 Accessibility permission과 release action을 준비했습니다.
  3. iPhone 앱에서 Local Network permission과 touch surface를 분리했습니다.
  4. Pairing, session, signed message gate를 붙였습니다.
  5. Move, click, scroll, drag를 순서대로 열었습니다.

처음 다루는 Swift/Xcode 환경에서도 이 순서가 있으면 다음 확인 지점을 만들 수 있었습니다.

Bluetooth 대신 Wi-Fi와 Bonjour를 선택했습니다

초기 선택지는 세 가지였습니다.

선택지장점제외하거나 보류한 이유
Bluetooth HID설명이 짧고 사용자에게 익숙합니다iPhone이 일반 Bluetooth 입력 장치처럼 등록된다는 기대를 만들 수 있습니다. iOS 공개 API와 배포 리스크도 컸습니다
Cloud relay / remote control네트워크 조건을 덜 탈 수 있습니다LazyPad의 local-first 입력 경계와 맞지 않았습니다
Wi-Fi + Bonjour + Mac Companion로컬 탐색, pairing, session, safety gate를 직접 통제할 수 있습니다같은 Wi-Fi 조건, Local Network permission, Mac Companion 설치가 필요합니다

v0.1에서는 세 번째 방식을 선택했습니다.

여기서 Bluetooth HID는 iPhone이 마치 Bluetooth 마우스나 트랙패드처럼 Mac에 등록되는 방식을 뜻합니다. 사용자에게는 직관적으로 들릴 수 있지만, iOS 공개 API와 App Store 배포 경계에서는 기대와 실제 구현 가능성이 어긋날 수 있었습니다.

Bonjour는 같은 로컬 네트워크 안에서 기기를 찾아주는 Apple 생태계의 discovery 방식으로 이해하면 됩니다. 사용자가 Mac의 IP 주소를 직접 입력하지 않아도 iPhone 앱이 가까운 Mac Companion을 찾을 수 있게 해줍니다.

Wi-Fi + Bonjour + Mac Companion 구조는 설치와 권한 안내가 필요합니다. 대신 어떤 Mac과 연결할지, 어떤 입력을 허용할지, 연결이 끊겼을 때 어떻게 입력을 release할지 직접 통제할 수 있었습니다.

첫 버전에서 필요한 것은 가장 화려한 연결 방식이 아니었습니다. 설명 가능하고 검증 가능한 입력 경계가 먼저 필요했습니다.

현재 구조는 다음과 같습니다.

iPhone touch surface
  -> local network message
  -> Bonjour discovered Mac Companion
  -> session/signature/sequence gate
  -> macOS CGEvent input

이 구조에는 약점이 있습니다. 사용자는 같은 Wi-Fi에 있어야 하고, Mac Companion도 설치해야 합니다. iPhone에서는 Local Network permission 안내가 필요합니다.

대신 제품 경계를 직접 통제할 수 있습니다. Paired Mac only, no cloud relay, forced release, kill switch, heartbeat를 구조 안에 둘 수 있었습니다.

네트워크 문제는 빠른 길보다 재현 가능한 길로 좁혔습니다

처음에는 HTTP web spike로 가능성을 확인했습니다. Mac에서 작은 server를 띄우고, iPhone Safari에서 touch delta를 보냈습니다.

그 다음 native iOS 앱과 Network.framework + Bonjour 구조로 옮겼습니다. 이 단계부터는 실제 제품에 가까운 조건을 확인할 수 있었습니다.

Native로 옮긴 뒤에도 문제가 남았습니다.

  • Browse/connect 반복으로 connection churn이 생길 수 있었습니다.
  • JSON payload와 per-move log가 입력 경로에 비해 과했습니다.
  • UI 갱신이 입력감에 섞였습니다.
  • Pending send가 쌓이면 손을 뗀 뒤 cursor가 밀릴 수 있었습니다.
  • Heartbeat와 input movement가 섞이면 reconnect flicker가 입력감 문제처럼 보였습니다.

이 문단의 용어는 다음처럼 읽으면 됩니다.

용어쉬운 설명
HTTP web spike제품을 만들기 전, 웹 브라우저와 간단한 서버로 가능성만 빠르게 확인한 실험입니다
Native iOS 앱Safari가 아니라 iPhone에 설치되는 실제 앱 형태입니다
Network.frameworkApple 플랫폼에서 네트워크 연결을 만들 때 쓰는 공식 프레임워크입니다
Connection churn연결이 안정적으로 유지되지 않고 자주 끊겼다가 다시 붙는 현상입니다
JSON payload입력 데이터를 사람이 읽기 쉬운 JSON 형태로 보낸 내용입니다
Per-move log손가락이 움직일 때마다 기록을 남기는 방식입니다. 디버깅에는 유용하지만 입력 경로에서는 부담이 될 수 있습니다
Pending send아직 전송이 끝나지 않은 입력입니다
Heartbeat연결이 살아 있는지 주기적으로 확인하는 신호입니다
Reconnect flicker연결 상태가 잠깐씩 바뀌며 화면이나 상태 표시가 깜빡이는 현상입니다

수정 방향은 단순화였습니다.

문제선택
High-rate move 지연move 전용 persistent TCP stream
작은 packet 누적TCP noDelay
payload 과다newline line protocol
늦은 이동량 flushlatest-value coalescing, gesture end discard
reconnect와 입력감 혼선heartbeat/reconnect loop와 input loop 분리
peer-to-peer Wi-Fi 불안정includePeerToPeer=false, 같은 Wi-Fi 기본값

Apple peer-to-peer Wi-Fi opt-in도 실험했습니다. 그러나 현재 장비와 네트워크에서는 Connection refused와 gap spike가 재발했습니다.

그래서 기본 경로는 같은 Wi-Fi로 두었습니다. 가능해 보이는 길보다 반복 검증 가능한 길을 선택했습니다.

터치 민감도는 배율 하나로 조정하지 않았습니다

처음에는 sensitivity multiplier를 조정하면 될 것처럼 보였습니다. 실제로는 문제가 더 나뉘어 있었습니다.

느린 이동에서는 cursor가 손가락보다 늦게 따라왔습니다. 빠른 이동에서는 cursor가 과하게 앞섰습니다. 손을 뗐다 다시 올리는 첫 순간에는 작은 움직임이 큰 jump처럼 보였습니다.

배율 하나로는 서로 다른 문제를 동시에 풀 수 없었습니다. 그래서 입력 파이프라인을 계층으로 나누었습니다.

입력 파이프라인은 손가락이 화면에 닿은 뒤 Mac cursor가 움직이기까지 거치는 처리 단계를 뜻합니다. LazyPad에서는 손가락 위치를 읽고, 작은 떨림을 줄이고, 이동량을 계산하고, Mac으로 보내고, Mac에서 실제 cursor event로 바꾸는 과정이 모두 여기에 들어갑니다.

계층참고 근거적용 판단
Sample intakeApple coalescedTouches, predictedTouchestouch sample을 timestamp와 함께 정규화했습니다
Gain curvecontrol-display gain, pointing transfer function 연구distance + velocity mixed gain을 적용했습니다
Smoothing1 Euro Filter속도 기반 adaptive smoothing을 적용했습니다
Gesture boundarytouch slop, deadzone, hysteresis 패턴tap/move/scroll/drag 전환을 분리했습니다
Scroll feelscroll physics, velocity tracker, residual wheeltwo-finger vertical scroll과 residual 처리를 분리했습니다

참고 자료는 선택을 좁히기 위한 기준으로 사용했습니다.

용어쉬운 설명LazyPad에서의 판단
Touch sample특정 시각에 손가락이 화면의 어디에 있었는지 나타내는 작은 측정값입니다손가락 움직임을 cursor movement로 바꾸는 가장 작은 재료입니다
Timestampsample이 발생한 시각입니다같은 거리라도 빠르게 움직였는지 천천히 움직였는지 판단하려면 시간이 필요했습니다
coalescedTouchesiOS가 한 번의 move callback 안에 묶어 제공하는 여러 touch sample입니다중간 움직임을 더 촘촘히 볼 수 있지만, 첫 재터치 batch가 커지는 원인 후보도 되었습니다
predictedTouchesiOS가 앞으로 이어질 것이라고 예측한 touch sample입니다지연을 줄이는 데 도움이 될 수 있지만, Mac cursor에 그대로 보내면 overshoot 위험이 있어 보수적으로 봤습니다
Jitter손가락이 거의 멈춰 있어도 입력값이 미세하게 흔들리는 현상입니다그대로 보내면 cursor가 떨리는 느낌을 줄 수 있습니다
Lag손가락 움직임보다 cursor 반응이 늦게 느껴지는 현상입니다smoothing을 강하게 걸수록 줄이기 어려워집니다
1 Euro Filter속도에 따라 smoothing 강도를 바꾸는 입력 필터입니다느릴 때는 jitter를 줄이고, 빠를 때는 lag를 줄이는 관점이 도움이 되었습니다
Control-display gain손가락 움직임과 화면 cursor 움직임 사이의 비율입니다작은 움직임은 정밀하게, 큰 움직임은 멀리 가도록 조정해야 했습니다
Transfer function입력값을 출력값으로 바꾸는 함수입니다단순 배율 하나가 아니라 속도와 거리별 곡선으로 pointer movement를 봐야 했습니다
libpointing운영체제별 pointing transfer function을 측정하고 비교하기 위한 연구와 도구입니다Mac pointer movement를 막연한 감이 아니라 측정 가능한 함수로 보는 근거가 되었습니다

Apple coalesced touchespredicted touches는 touch sample을 어떻게 다룰지 확인하는 데 사용했습니다. 1 Euro Filter는 jitter와 lag의 tradeoff를 나누는 데 도움이 되었습니다. libpointing 논문control-display gain 연구는 pointer gain을 단순 배율이 아니라 transfer function으로 보는 근거가 되었습니다.

AI는 후보를 넓혔습니다. 기본 지식과 문헌 검토는 후보를 줄였습니다.

Cursor reacquire 문제는 네트워크 하나로 설명되지 않았습니다

가장 오래 남은 문제는 cursor reacquire였습니다.

증상은 구체적이었습니다. iPhone surface에서 손을 뗐다가 다시 올린 직후, 아주 작은 움직임이 큰 cursor jump처럼 보였습니다.

Cursor reacquire는 손가락을 뗐다가 다시 올렸을 때, 앱이 "새 입력이 시작됐다"고 다시 잡는 순간을 뜻합니다. 이 순간에는 이전 입력 상태를 이어갈지, 완전히 새 gesture로 볼지 판단해야 합니다.

처음에는 네트워크 지연처럼 보일 수 있었습니다. 그러나 연속 이동은 부드러웠고, click과 scroll도 도달했습니다. 문제는 첫 재터치 구간에 집중되어 있었습니다.

그래서 원인을 layer별로 나누었습니다.

후보판단
Wi-Fi 지연ordinary Wi-Fi에서도 재현되어 단일 원인으로 보지 않았습니다
Mac pointer smoothing statezero-delta gesture-start로 일부 개선됐지만 충분하지 않았습니다
첫 activation deltaclamp 방향은 맞았지만 같은 callback의 후속 sample이 남았습니다
reusable input stream stale completiongeneration guard로 방어했지만 최종 원인으로 보지는 않았습니다
첫 coalesced touch batch 전체build 8 이후 가장 설명력 높은 원인으로 판단했습니다

빌드별 흐름은 다음과 같습니다.

빌드조치결과
build 4touch began 시 zero-delta gesture-start 전송buffered catch-up은 줄었지만 tiny jump가 남았습니다
build 5-6first activation delta clamp개선 방향은 맞았지만 충분하지 않았습니다
build 7input stream reset, stale completion generation guardstream 후보는 약해졌지만 jump가 남았습니다
build 8첫 emitted reacquire move batch 전체 clamp큰 cursor teleport가 재현되지 않았습니다
build 10drag behavior와 automatic-drag-after-wait regression 재확인input-feel 기준선으로 채택했습니다

이 문제는 네트워크 하나로 설명되지 않았습니다. Gesture boundary, first coalesced touch batch, Mac pointer gain이 겹친 문제로 보는 편이 더 정확했습니다.

그 덕분에 감도 배율만 계속 조정하는 방향을 피할 수 있었습니다.

AI는 코드보다 판단 루프를 빠르게 만들었습니다

AI를 가장 많이 사용한 지점은 코드 작성 자체보다 판단 루프였습니다. 선택지를 만들고, 작업 단위를 나누고, 검증 기준을 잃지 않는 데 도움이 되었습니다.

AI로 빠르게 한 일직접 다시 확인한 일
Swift/Xcode 구조 탐색실제 build, test, signing 결과
iOS/Mac target 분리 후보제품 repo 구조와 배포 가능성
Bluetooth/Wi-Fi/Bonjour 선택지 비교iOS 공개 API, Mac Companion 필요성, 로컬 네트워크 조건
입력감 알고리즘 후보 조사Apple 문서, HCI 논문, 실기기 smoke
TestFlight/Notarization checklist 작성App Store Connect 상태, Developer ID artifact 검증
문서화와 evidence boundary 정리raw log, 계정 값, device identifier 비공개 원칙

이 방식의 장점은 속도입니다. 처음 다루는 도구에서도 질문을 빠르게 만들 수 있었고, 막히는 지점을 다음 검증 단위로 나눌 수 있었습니다.

단점도 분명했습니다. AI는 그럴듯한 선택지를 많이 열어줍니다. 그중 일부는 제품 경계와 맞지 않습니다. Bluetooth처럼 좋아 보이지만 현재 구조와 맞지 않는 표현도 있고, predicted touch처럼 매력적이지만 overshoot 위험이 있는 알고리즘도 있습니다.

그래서 AI 활용의 핵심은 질문 생성 이후에 있었습니다. 어떤 답을 채택하고 어떤 답을 버릴지 판단해야 했습니다.

배포 후보는 코드 밖의 gate까지 포함했습니다

iPhone 앱만으로는 외부 사용자가 LazyPad를 사용할 수 없습니다.

Mac Companion이 필요합니다. Mac Companion은 Accessibility permission이 필요합니다. 외부 배포물은 Developer ID signing, notarization, stapling, Gatekeeper, zip/unzip verification을 통과해야 합니다.

iOS 쪽에도 절차가 있습니다. App Store Connect에는 beta app description, feedback email, review contact, review notes, What to Test가 필요했습니다. 외부 TestFlight 그룹에 build를 연결하고 Apple Beta App Review를 기다려야 했습니다.

웹 표면도 준비했습니다. lazypad.app에는 landing, support, privacy 초안을 만들었습니다. 문구는 현재 상태를 넘지 않도록 보수적으로 잡았습니다.

문구 경계이유
Paired Mac only입력 대상이 임의 Mac이 아닙니다
No cloud relayruntime input control에 cloud relay를 쓰지 않습니다
Mac Companion requires AccessibilitymacOS 입력 실행에 필요한 권한입니다
External TestFlight pending현재 상태를 완료처럼 쓰지 않기 위한 경계입니다

제품은 코드만으로 완성되지 않습니다. 설치, 권한, 지원 주소, privacy 설명, review note까지 포함해야 외부 사용자가 설치할 수 있는 후보가 됩니다.

소개 영상은 사용 장면을 보여주는 보조 자료입니다

아래 두 영상은 LazyPad가 겨냥한 사용 맥락을 보여주는 5초 소개 컷입니다.

실제 동작 증거로 사용하지 않습니다. 실제 검증 근거는 build, test, physical smoke, App Store Connect 상태, Mac Companion 배포물 확인으로 분리했습니다.

영상은 사용 장면을 빠르게 보여줍니다. 다만 제품이 실제로 동작한다는 근거는 별도의 검증 결과로 분리했습니다.

이번 글에서 남는 결론

LazyPad 1편에서 보여주고 싶은 내용은 기능 목록보다 작업 방식입니다.

처음 다루는 Swift/Xcode 환경에서도 다음 루프를 만들면 배포 후보까지 갈 수 있었습니다.

  1. AI로 후보를 넓힙니다.
  2. 기본 지식으로 선택지를 나눕니다.
  3. 공식 문서와 논문으로 방향을 확인합니다.
  4. 실제 기기에서 smoke를 반복합니다.
  5. 완료, 대기, 사용하지 않을 표현을 분리합니다.

이 경험에서 확인한 AI 활용 역량은 작업을 많이 맡기는 능력만이 아니었습니다. AI가 제안한 길이 맞는지 확인하는 능력이 함께 필요했습니다.

관련 지식을 직접 확인할 수 있으면 더 빠르게 도달할 수 있습니다. 부족한 기본기도 더 빨리 드러납니다. LazyPad는 그 과정을 실제 앱 개발과 배포 후보 검증으로 확인한 프로젝트입니다.

다음 글은 입력감 용어를 먼저 풀어갑니다

다음 글에서는 touch sample, coalescedTouches, predictedTouches, jitter, lag, 1 Euro Filter, control-display gain, transfer function을 먼저 설명합니다.

이 용어를 풀어야 3편의 cursor reacquire 문제를 더 쉽게 읽을 수 있습니다. 손을 뗐다가 다시 올리는 첫 순간, 왜 작은 movement가 큰 cursor jump처럼 보였는지는 그다음 글에서 build 4부터 build 10까지의 가설 검증으로 다룹니다.

4편에서는 TestFlight와 Mac Companion 배포물을 다룹니다. iOS build 하나가 아니라 Mac 배포물, support email, review note, privacy page까지 묶여야 왜 외부 사람이 설치할 수 있는 후보가 되는지 설명하겠습니다.

이어 읽기

시리즈는 순서대로, 편집 추천은 맥락대로, 비슷한 주제는 태그 기준으로 정리합니다.

시리즈 전체

LazyPad 개발기: 아이폰을 맥 입력면으로 만들기1/4
  1. 1.LazyPad 개발기 1: 처음 쓰는 Swift와 Xcode로 iPhone-Mac 앱 배포 후보 만들기
  2. 2.LazyPad 개발기 2: 터치 입력감은 왜 배율 하나로 해결되지 않았나
  3. 3.LazyPad 개발기 3: 첫 재터치에서 드러난 커서 입력감 디버깅
  4. 4.LazyPad 개발기 4: TestFlight와 Mac Companion 배포 후보 검증

함께 읽으면 좋은 글

편집 추천

비슷한 주제의 글

태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.