iOS앱 반영 가이드 · 반복 관련 3건

맥북에서 iOS 앱(day_plan_appl_app)에 적용 — Flutter 앱 반영 코드 기준

todo_edit_sheet.dart calendar_tab.dart 2026-10-05 (KST)

개요

iOS가 Flutter면 아래 Dart 그대로 복사. 네이티브(Swift)면 "동작/로직"대로 구현. A·B는 반드시 반영, C는 현재 로직이 아래와 같은지 확인(다르면 맞춤).

A반복 항목 날짜 변경 불가 + 다이얼로그

편집 시트의 생성일(기준일) 피커를 여는 지점에서, 반복이면 안내 후 중단. 비반복만 날짜 이동.

위치: lib/widgets/todo_edit_sheet.dart → _pickCreated() 진입부

Future<void> _pickCreated() async {
  // 반복 항목은 날짜(기준일) 변경 불가 — 안내 후 중단. 비반복만 이동 허용.
  if (t.isRepeating) {
    await showDialog<void>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('반복 항목'),
        content: const Text(
            '반복 항목은 날짜 변경이 안됩니다.\n대신 삭제 후에 할일을 재생성하세요.'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('확인')),
        ],
      ),
    );
    return;
  }
  // ... (이하 기존 생성일 피커 로직: 비반복만 실행)
}

Swift: 생성일 변경 핸들러 맨 앞에서 todo.isRepeating이면 UIAlertController(확인)로 안내 후 return.

B반복 기한 기본값(생성일 기준·단위별) + 무기한

반복기본 기한(생성일 기준)
매일 day+7일
매주 week+21일(3주)
매월 month+3개월
매년 year+3년

무기한 선택 가능(기존 기한 피커의 '무기한' 그대로 = 기한 nil). 단위 바꾸면 재계산하되 수동 지정 기한은 보존.

위치: lib/widgets/todo_edit_sheet.dart → _defaultRepeatUntil(), 반복 단위 DropdownButton.onChanged

기본값 함수(단위별·생성일 기준)

/// 반복 기한 기본값: 생성일 기준, 단위별(매일+7·매주+21·매월+3개월·매년+3년).
String _defaultRepeatUntil([String? unit]) {
  final u = unit ?? t.repeatUnit;
  final base = DateTime.tryParse(_dateOnly(t.createdAt)) ?? DateTime.now();
  switch (u) {
    case 'day':
      return isoOf(base.add(const Duration(days: 7)));
    case 'week':
      return isoOf(base.add(const Duration(days: 21)));
    case 'year':
      return isoOf(DateTime(base.year + 3, base.month, base.day));
    default: // 매월
      return isoOf(DateTime(base.year, base.month + 3, base.day));
  }
}

단위 변경 시 재계산(수동값 보존)

onChanged: t.isRecurring
    ? null
    : (v) => setState(() {
          final wasRepeat = t.isRepeating;
          final oldUnit = t.repeatUnit;
          t.repeatUnit = (v == null || v.isEmpty) ? null : v;
          if (t.repeatUnit == null) {
            t.repeatUntil = null; // 안함 → 기한 제거
          } else {
            final cur = t.repeatUntil;
            // 비어 있거나 '이전 단위 자동 기본값'이면 새 단위 기본값으로 재계산(수동 지정 보존).
            if (cur == null || cur.isEmpty || cur == _defaultRepeatUntil(oldUnit)) {
              t.repeatUntil = _defaultRepeatUntil(t.repeatUnit);
            }
          }
          if (!wasRepeat && t.isRepeating) {
            t.calVisible = false;
          } else if (wasRepeat && !t.isRepeating) {
            t.calVisible = true;
          }
        }),

저장 시에도 반복인데 기한 비면 t.repeatUntil = _defaultRepeatUntil();(현재 단위). Swift: Calendar.date(byAdding:)로 day=+7/week=+21/month=+3M/year=+3Y.

C달력표시 OFF 반복 = 현재 회차에 계속 표시

cal_visible=false 반복은 모든 회차가 아니라 현재 회차 한 곳만 달력에 표시. 현재 회차 = 오늘 이하 최근 회차(시작 전이면 기준일). 과거가 되어도 계속 표시, 다음 회차가 되면 그 칸으로 이동(누적 아님).

오늘(매달 7일, OFF)표시 위치
11/8 ~ 12/611/7 (계속)
12/712/7 (11/7 사라짐)

위치: lib/screens/calendar_tab.dart → 월 셀 전개(반복 처리부)

// 표시(cal_visible=true) 반복: 모든 회차를 셀에 전개.
occurrencesIn(repeating.where((t) => t.calVisible).toList(),
        gridStart, gridEnd, today: now, expandAll: true)
    .forEach((iso, list) {
  for (final t in list) { addRep(t, iso); }
});
// 숨김(cal_visible=false) 반복: 셀엔 '현재 회차(생성/최근 발생) 한 곳'만.
// 시작일이 미래면 '만든 날짜(기준일)' 칸에. 과거가 되어도 계속 표시(다음 회차 전까지).
for (final t in repeating.where((t) => !t.calVisible)) {
  final base = DateTime.tryParse(t.repeatAnchor ?? '');
  if (base == null) continue;
  final until = t.repeatUntil == null ? null : DateTime.tryParse(t.repeatUntil!);
  final cur = latestOccurrenceOnOrBefore(
          base, t.repeatUnit, t.repeatInterval, now, until: until) ??
      dOnly(base);
  final at = t.isDelayed ? DateTime.tryParse(t.delayDate!) ?? cur : cur;
  if (at.isBefore(gridStart) || at.isAfter(gridEnd)) continue;
  addRep(t, isoOf(at));   // 과거여도 숨기지 않음(현재 회차에 계속 표시)
}
핵심: 숨김 반복은 현재 회차 한 칸(latestOccurrenceOnOrBefore, 없으면 기준일)에만 표시하고, "과거면 숨김" 필터를 걸지 말 것(현재 회차는 과거가 되어도 계속 보여야 함). 목록 탭엔 cal_visible 무관하게 항상 표시.

참고