반응형

ios에서 App이 종료된 상태에서

push notification이 왔을대

메시지를 클릭해 앱을 여는 과정에서

앱이 splash screen 상태에서 멈춰버렸다가

결국 뻗어버리는 이슈를 경험했다.

 

삽질에 삽질을 더한결과

notification 문제는 아니고

react-native-splash-screen의 이슈였다.

 

https://github.com/crazycodeboy/react-native-splash-screen/issues/397

 

위 링크의 가이드가

99% 같은 내용이라

내게도 해결책이 될것이라고 판단했다.

 

(그외 참고 자료들)

https://github.com/crazycodeboy/react-native-splash-screen/issues/488

https://stackoverflow.com/questions/66598301/react-native-firebase-push-notification-crash-report-problem

https://ios-development.tistory.com/384

 

 

그러나 또

적용 과정에서 문제가 발생했으니...

바로 앱을 실행하면 바로 뻗어버리는 현상이었다.

 

왜 그럴까?

이슈를 찾다가 자꾸 시간이 지연되어

 

그냥 라이브러리없이

splashscreen을 구현하는 방향으로

돛을 돌렸다.

 

 

반응형
반응형

xcode에 디바이스 연결 후

테스트를 하려다보니

빌드는 성공적으로 완료되었는데

 

failed to get the task for process

라는 에러가 등장했다.

 

[ 솔루션 ]

Build Settings -> code signing identity -> debug -> "ios Develpoer"

 

"ios Developer" 부분이 핵심이다

저 부분이 "ios Distribution"으로 되어있을때 에러가 발생했었다.

 

반응형
반응형
Data Class Xcode Key Raw Info.plist Key
Apple Music Privacy - Media Library Usage Description NSAppleMusicUsageDescription
Bluetooth Privacy - Bluetooth Peripheral Usage Description NSBluetoothPeripheralUsageDescription
Calendar Privacy - Calendars Usage Description NSCalenedarUsageDescription
Camera Privacy - Camera Usage Description NSCameraUsageDescription
Contacts Privacy - Contact Usage Description NSContactsUsageDescription
Health Privacy - Health Share Usage
Description Privacy - Health Update Usage Description
NSHealthShareUsageDescription
NSHealthUpdateUsageDescription
Home Privacy - HomeKit Usage Description NSHomeKitUsageDescription
Location Privacy - Location Always Usage
Description Privacy - Location When In Use Usage Description
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription 
Microphone Privacy - Microphone Usage Description NSMicrophoneUsageDescription
Motion Privacy - Motion Usage Description NSMotionUsageDescription
Photos Privacy - Photo Library Usage Description NSPhotoLibraryUsageDescription
Reminders Privacy - Reminders Usage Description NSRemindersUsageDescription
Siri Privacy - Siri Usage Description NSSiriUsageDescription
Speech
Recognition
Privacy - Speech Recognition Usage Description NSSpeechRecognitionUsageDescription
TV Provider
Account
Privacy - TV Provider Usage Description NSVideoSubscriberAccountUsageDescription

 

NSAppleMusicUsageDescription : 미디어 라이브러리 접근

NSBluetoothPeripheralUsageDescription : 블루투스 인터페이스 접근

NSCalenedarUsageDescription : 달력 접근

NSCameraUsageDescription : 카메라 접근

NSContactsUsageDescription : 연락처에 접근

NSHealthShareUsageDescription : 헬스 데이터에 접근

NSHealthUpdateUsageDescription : 건강 데이터에 접근

NSHomeKitUsageDescription : 홈킷 설정 데이터에 접근

NSLocationAlwaysUsageDescription : 위치정보 접근 (항시 허용)
NSLocationWhenInUseUsageDescription : 위치정보 접근 (사용시 허용)

NSMicrophoneUsageDescription : 마이크 접근

NSPhotoLibraryUsageDescription : 사진 라이브러리 접근

NSSiriUsageDescription : 시리 접근

 

 

예전과 다르게

앱 배포할때 필수적으로 세팅해야되는 사항들이

많아졌다.

 

개인정보의 가치가

점점 소중해지고

쾌적한 앱 환경 조성을 위해

발전하고 있는 과정이라고 생각한다.

 

(개발자에겐 불편)

반응형
반응형

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 아이콘이 왜 안나와?

 

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

 

 

푸시 알림이

ㄱ) foreground

ㄴ) background

ㄷ) quit state

에서 모두 정상적으로 수신되는것을

분명히 확인했었다

 

라고 착각을 하고있었는데

앱 배포 직전

검토 과정에서

iOS에 알림이 수신은 되지만

진동이 울리지 않음을 발견하였다.

 

위 문제를 해결하기 위해

아래와 같은 방법으로 접근하였다.

 

1. 내가 설정을 덜 한것은 없는지

2. rnfirebase, github에 올라온 issues중에 같은 내용은 없는지

3. vibration 기능의 접근 권한이 문제인지

4. setBackgounrMessageHandler 수신 부분이 문제인지

5. 푸시 발송관련 서버 코드에 문제가 있는것인지

 

1. 내가 설정을 덜 한것은 없는지

rnfirebase docs를 통해 체크한 결과

headless 상태에 대한 처리가 미흡해 보였다.

// index.js
import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background!', remoteMessage);
});

messaging()
  .getIsHeadless()
  .then(isHeadless => {
    // do sth with isHeadless
  });
  
function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    // App has been launched in the background by iOS, ignore
    return null;
  }

  return <App />;
}

function App() {
  // Your application
}

AppRegistry.registerComponent('app', () => HeadlessCheck);
// appDelegate.m

// add this import statement at the top of your `AppDelegate.m` file
#import "RNFBMessagingModule.h"

// in "(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions" method
// Use `addCustomPropsToUserProps` to pass in props for initialization of your app
// Or pass in `nil` if you have none as per below example
// For `withLaunchOptions` please pass in `launchOptions` object
NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];

// Find the `RCTRootView` instance and update the `initialProperties` with your `appProperties` instance
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:@"nameOfYourApp"
                                             initialProperties:appProperties];

위 세가지 설정들에 대해서 다시 한번

검토를 하였다.

 

2. rnfirebase, github에 올라온 issues중에 같은 내용은 없는지

제법 많은 비슷한 내용들이 있었다.

그 중에서 내가 찾은 솔루션은

https://github.com/invertase/react-native-firebase/issues/5656

위의 방법인데

이것은  3,4,5의 과정을 걸쳐서 찾은 솔루션이다.

 

3. vibration 기능의 접근 권한이 문제인지

react-native에 vibration이라는 기능이 내포되어있다.

이 마저도 해보려고하니 잘못된 정보들이 너무나도 많았다.

임시 버튼을 만들어

기능이 잘 동작하는지만 확인하였다.

import { Platform, Vibration } from 'react-native';

// test code
const umm = () => {
  Platform.OS === 'ios' && Vibration.vibrate([400]);
}

 

4. setBackgounrdMessageHandler 수신 부분이 문제인지

화면을 열어보면 푸시가 와있어서

setBackgounrdMessageHandler 리스너에

문제가 있으리라고는 생각해보지 않았었다.

 

하지만, 디바이스에서는 수신했더라도

앱 내에 setBackgounrdMessageHandler 리스너가

실제로 잘 동작하고 있는지는 검토해볼 필요가 있었다.

messaging().setBackgroundMessageHandler(async remoteMessage => {
  umm(); // vibration
  console.log('Message handled in the background!', remoteMessage);
});

const umm = () => {
  Platform.OS === 'ios' && Vibration.vibrate([400]);
}

 

위와 같이 임시 코드를 작성해서

진동이 오는지 체크해본 결과

위의 리스너가 제대로 동작하고 있지 않음을 발견했다.

 

리스너가 제대로 동작하지 않는다면

설정의 문제 혹은 발송의 문제

둘중에 하나라고 결론을 냈다.

 

그런데 아까 분명히

설정의 문제는 확인을 완료했다.

그러면 발송의 문제일 것이다.

 

5. 푸시 발송관련 서버 코드에 문제가 있는것인지

2번에서 언급했던

https://github.com/invertase/react-native-firebase/issues/5656

위의 링크에서 알맞는 발송 포맷을 발견했다.

 

추후, 가이드를 다시보니 업데이트가 된것인지

위 github 링크와 동일한 내용으로 발송 포맷이 나와있었다.

 

github 마지막 코멘트 업데이트 일자가

patels9-vf commented 7 hours ago

겨우 7시간전이 아닌가...

위 이슈가 최근까지도 이어지고있었나보다.

 

공식 가이드에서는

아래와 같이 푸시메시지를 발송하라고 한다.

admin.messaging().send({
  data: {
    //some data
  },
  apns: {
    payload: {
      aps: {
        contentAvailable: true,
      },
    },
    headers: {
      'apns-push-type': 'background',
      'apns-priority': '5',
      'apns-topic': '', // your app bundle identifier
    },
  },
  //must include token, topic, or condition
  //token: //device token
  //topic: //notification topic
  //condition: //notification condition
});

 

다만, 위의 메시지는

data-only에 해당하는 것이기도 하고

보다 많은 케이스에 대비하고자

github의 가이드를 따랐다.

 

const message: any = {
  token,
  notification: {
    title,
    body,
  },
  data: {
    title,
    body,
  },
  android: {
    priority: 'high',
    notification: {
      title,
      body,
      sound: 'default'
    }
  },
  apns: {
    headers: {
      'apns-priority': '5',
    },
    payload: {
      aps: {
        sound: 'default',
        // contentAvailable: 1,
        'content-available': 1,
      }
    }
  }
};
admin.messaging().send(message);

 

위 방법으로 문제없이 메시지가 수신됨을 확인하였다.

 

 

** 번외 **

나는 애플워치를 사용하는데

핸드폰에서 알림이 안울리고

계속 애플워치에서 알림이 울려서

블루투스를 끊고 핸드폰에서 알림이 오는지에 대한

확인이 필요하였다.

 

테스트 결과 수신은 됐다.

다만, 어느정도의 딜레이가 발생했다.

 

가이드에 따르면 priority 설정된

푸시 메시지는

8seconds 정도의 간격으로만

발송이 가능한듯하다.

 

 

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 아이콘이 왜 안나와?

 

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

반응형
반응형

[ react-native-code-push 적용하기 ]

https://honeystorage.tistory.com/290

 

[ MS, appcenter 공식 Document ]

https://docs.microsoft.com/ko-kr/appcenter/distribution/codepush/

 

지난 포스팅에 이어

실무에서 사용하기 위한

몇가지 업데이트 사항을 알아보고자 한다

 

1. Mandatory (필수 적용 옵션)

배포 내용에 따라 주요 업데이트때와 마찬가지로

필수적으로 업데이트를 했으면 하는

경우가 발생할 수 있다.

 

배포간에 Mandatory옵션을 사용하면

"업데이트 / 나중에"와 같이 선택지를 주는게 아니라

"업데이트" 선택지 하나만 줄 수 있다.

 

// 일반적인 배포
appcenter codepush release-react -a username/appname -d Production

// 필수사항 배포
appcenter codepush release-react true -a username/appname -m -d Production

"-m" 부분이 추가되었다.

 

코드상에서 updateDialog를 수정해서

사용자에 보여줄 텍스트를 커스텀 할 수도있다.

codePush.sync({
 updateDialog: {
   titie: '업데이트 하세요!',
   mandatoryUpdateMessage: '필수 업데이트입니다. 진행해주세요.',
   mandatoryContinueButtonLabel: '업데이트',
 },
 mandatoryInstallMode: codePush.InstallMode.IMMEDIATE,
});

 

 

*** 기타 내용 업데이트 예정

반응형
반응형

패키지를 추가하거나

native코드를 수정하는 등의

큰 변화를 주지않고

 

js, img, css 를 수정하는 배포에대해

빌드를 하지 않고도 배포할수 있게 해주는

code-push기능 적용 일기이다.

 

참고 링크 1 Weiji 웨이지 님의 블로그

참고 링크 2 Gale Lee 님의 블로그

참고링크 3 appcenter 공식 문서

본 작업은 ,  react-native 0.66 버전에서 진행되었습니다.

 

1. 설치

npm i -g appcenter-cli

 

2. 로그인

appcenter login

위 명령어를 입력하면

? Enable Temporary(Y/n)? 라는 질문이 나타난다.

원투데이 개발할게 아니므로 당연히 n

 

그러고나면 terminal에

?Access code from brwoser이라는 질문과함께

연동된 페이지가 나타나며

구글/깃헙/페북 등 로그인 수단을 선택할것이라고 뜬다.

 

나는 github을 선택했고, username을 설정하고나니

code가 떴다.

코드 복붙

그러면 로그인 완료

 

3. 로그인 확인

appcenter profile list

위 명령어로 로그인된 계정의

Username, Display Name, Email 등을 확인할 수 있다.

 

4. 서비스 등록하기 (대소문자 주의)

appcenter apps create -d appname-aos -o Android -p React-Native
appcenter apps create -d appname-ios -o iOS -p React-Native

이름은 자유지만 android/ios 구분해서 짓기

 

5. 등록된 서비스 조회

appcenter apps list

 

6. 홈페이지 접속해보기

https://appcenter.ms/apps

 

7. react-native-code-push 설치하기

npm i --save react-native-code-push

 

8.  배포단계 설정하기

appcenter codepush deployment add -a username/appname-aos Staging
appcenter codepush deployment add -a username/appname-aos Production
appcenter codepush deployment add -a username/appname-ios Staging
appcenter codepush deployment add -a username/appname-ios Production

이렇게 배포단계에 대한 네이밍을 세팅해두면

appcenter codepush deployment list -a username/appname-aos

이런 방식으로 현재 진행중인 상태를 조회할수 있다고 한다.

 

9. 앱의 배포를 위해 key 조회하기

appcenter codepush deployment list -a username/appname-aos -k
appcenter codepush deployment list -a username/appname-ios -k

 

10. ios 설정

- 라이브러리를 설치했으니 pod도 설치해준다.

cd ios && pod install && cd ..

 

- xcode의 AppDelegate.m 수정

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  #if DEBUG
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  #else
    return [CodePush bundleURL];
  #endif <-- 새로 변경된 파트
  
//#if DEBUG
//  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
//#else
//  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
//#endif <-- 원래 파트
}

 

- app에 AppCenter-Config.plist 추가

NewFile -> Property List -> Save as (AppCenter-Config.plist)

 

- Project > info > Configuration > +

위 이미지처럼 Staging항목을 추가해줍니다.

 

- Info.plist에 Key 추가하기

key: CodePushDeploymentKey / value: ${CODEPUSH_KEY}

를 추가해줍니다.

 

Project > Build Settings > + Add user defined setting을 클릭

 

MULTI_DEPLOYMENT_CONFIG 추가

release $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

staging $(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)

 

같은 방법으로

CODEPUSH_KEY 추가

appcenter codepush deployment list -a username/appname-ios -k

위 명령어로 조회된 키를 

release, staging에 알맞게 입력

 

 

11. android 설정

- android/setting.gradle

// add code push
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')

 

- key 추가하기

먼저, 아래 명령어로 키를 조회

appcenter codepush deployment list -a username/appname-aos -k

아래 내용 추가

// android/gradle.properties

CODEPUSH_DEPLOYMENT_KEY_DEBUG=
CODEPUSH_DEPLOYMENT_KEY_STAGING=????
CODEPUSH_DEPLOYMENT_KEY_PRODUCTION=????

 

- /android/app/build.gradle

// buildTypes
buildTypes {
  debug {
    ...
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_DEBUG
  }
  release {
    ...
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_PRODUCTION
  }
  releaseStaging {
    initWith release
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_STAGING
    matchingFallbacks = ['release']
  }
}




// enableHermes 관련 코드가 있다면
if (enableHermes) {
  ...
  releaseStagingImplementation files(hermesPath + "hermes-release.aar")
} else {
  ... 
}



// 맨 아래에 추가
apply from: "../../node_modules/react-native/react.gradle" <-- 에러나면 주석처리
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"

- MainApplication.java

import com.microsoft.codepush.react.CodePush; // <- add

new ReactNativeHost(this) {
  ...
  
  //add
  @Override
  protected String getJSBundleFile() {
    return CodePush.getJSBundleFile();
  }
}

 

+++ 안드로이드에서 코드푸시가 되지않아 에러 추적을해본 결과

[CodePush] Exception com.microsoft.codepush.react.CodePushUnknownException: Error in getting binary resources modified time

위와 같은 에러가 발견됨

// android/app/build.gradle

defaultConfig {
  ...
  resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
}

이 방법으로 해결함

 

설정할게 많다...

 

그치만 우리가 얻는 이득에 비하면 이건 아무것도아니다.

(하, 그래도 이거는 솔직히 할거 너무많다)

 

설정은 끝났다.

업데이트를 수신할건지에 대한 코드만 추가해주면된다.

 

12. 코드 추가하기

(여기서부터는 자유롭게 하면 될것같다)

// 필자는 App.tsx 에서 작업하였다.

import CodePush from 'react-native-code-push';
import { AppState } from 'react-native';

useEffect(() => {
  const staging = AppState.addEventListener('change', (state) => {
    if (state === 'active') codePushSync();
  });
  
  return () => {
    staging.remove();
  }
}, [])

const codePushSync = () => {
  CodePush.sync({
    updateDialog: {
      title: '새로운 업데이트가 존재합니다.',
      optionalUpdateMessage: '지금 업데이트 하시겠습니까?',
      optionalIgnoreButtonLabel: '나중에',
      optionalInstallButtonLabel: '업데이트'
    },
    installMode: CodePush.InstallMode.IMMEDIATE
  })
}



export default CodePush({ checkFreQuency: CodePush.CheckFrequency.MANUAL })(App);

 

빌드파일을 지우고

re-빌드해서 배포테스트를 해보자.

반응형
반응형

개발중

안드로이드에서 Modal을 닫을때

순간적으로 깜빡이는 이슈가

별견되었다.

 

github. Issue에

많은 솔루션들이 있었지만

 

나는 아래의 방법을 적용하니

깔끔하게 해결되었다.

 

return (
  <Modal 
    style={Style}
    isVisible={visible} 
    animationIn={InEffect}
    animationOut={OutEffect}
    backdropOpacity={Opacity}
    backdropTransitionOutTiming={0} <-- 여기
  >
    <ModalContainer>{Viewer}</ModalContainer>
  </Modal>
);

 

참고링크:

https://github.com/react-native-modal/react-native-modal/issues/268

 

 

추가로 이런 이슈도 있었다.

대부분의 modal라이브러리가 그렇듯

react-naive-modal에도 visible 프로퍼티가 존재한다.

 

반드시 modal의 노출 여부는

이 visible로만 핸들링 해야한다.

아래와 같은 코드에서는 비정상적인 움직임을 보여준다.

 

// 올바른 사례
const [show, setShow] = useState(false);

<Modal
  visible={show}
>
</Modal>



// 잘못된 사례
const [show, setShow] = useState(false);
{
  !!show &&
  <Modal
    visible={show}
  >
  </Modal> 
}

 

어떤 이유에선가 아래와 같이 작업할지도 모른다.

그렇게하면 on/off는 컨트롤 할 수 있겠지만

비정상적인 애니메이션을 보게 될것이다.

반응형
반응형

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 왜 진동이 안울려?

 

 

[Android에 해당하는 내용]

 

따로 설정하지 않으면

notification icon으로

기본 앱 icon으로 설정된다는 내용부터

 

transparent 이미지만

사용 가능하다는 등

다양한 솔루션을 거쳐

 

결론적으로 어떻게해야

아이콘을 display 할수있는지

알아냈다.

 

먼저, 아래 링크에서

이미지를 규격별로 만들것이다.

링크: 이미지 만들기

 

다음으로는, 여기

공식 가이드라인에서 안내하는데로

firebase.json을 수정해줄것이다.

 

그전에, /android/src/main/res/values/colors.xml에 

나의 앱에 맞는 custom-color를 만들고오자.

<resources>
  <color name='white'>#ffffff</color>
  <color name='custom'>#eeeee</color>
</resources>

 

다시 돌아가

firebase.json에 아래 링크를 참조해서

추가 설정을 해본다.

참고자료: 링크

// <projectRoot>/firebase.json
{
  "react-native": {
    "messaging_android_notification_color": "@color/custom"
  }
}

 

설정은 반쯤 되었다.

이번에는

아까 만든 규격별 이미지를 

/android/src/main/res/

아래에 모두 넣어주자.

이런느낌?

 

마지막으로

AndroidManifest.xml를 수정하고

실행해보면

정상적으로 notification 아이콘이 뜨는걸 볼수있다.

<application
 ...
>
  ...
  <meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@mipmap/이미지명" 
  />
  <meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/custom"
  /> <-- color 지정할때 반드시 firebase.json에서 설정한것과 같은 값으로 할것
  ...
</application>

 

실행 혹은 빌드한뒤

푸시를 보내보자

정상적으로 뜬다면 행복해하면된다.

 

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 왜 진동이 안울려?

반응형
반응형

수치의 최소, 최대치를 정하기위한

UI적 요소로 Multi slider를 고려하곤 한다.

(Multi slider란 이런식으로 양방향 슬라이딩을 하는것을 말함)

 

많은 라이브러리를 검토해본 결과

아래의 라이브러리를 선정하였으며

관련하여 짤막한 정리를 하고자한다.

 

npm i @ptomasroos/react-native-multi-slider@2.2.2

설치때는 2.2.2 버전으로 받기를 권장한다.

최신 버전에서는 오류를 경험했었다.

github의 issue를 트랙킹하여

2.2.2에서는 문제 없이 돌릴수 있음을 확인하였다.

 

Docs에 예제코드는 물론 자세한 설명이 나와있지만

나의 예제 코드를 본다면

<MultiSlider
  values={values}
  min={20}
  max={100}
  step={20}
  containerStyle={{alignItems: 'center', zIndex: 5}}
  trackStyle={{backgroundColor: 'blue', height: 2.5}}
  selectedStyle={{backgroundColor: 'red'}}
  sliderLength={screenWidth - 52}
  snapped
  customMarker={() => (
    <FastImage style={{width: 28, height: 28}} source={image} />
  )}
  onValuesChange={updateValue}
/>

 

참고로 위 이미지에서

칸칸이 표시한것은 별도의 컴포넌트를

슬라이더 뒤에 깔아준것이다.

 

반응형
반응형

구현간에 어처구니없는 이슈로

삽질을 1시간이나해서 남기는 기록...

 

목표

1. animation 처리할 박스의 크기를 구한다.

2. 박스 크기 측정이 완료되면 Collapsible 느낌으로 나타나게한다.

* 조건: 처리할 박스 하단에는 다른 컴포넌트는 없음

 

어떻게 처리할지 고민이 되었다.

박스의 크기를 구하려면

이미 박스가 렌더링 되어있어야하는데

그러면 Collapsible 느낌을 낼수가 없다는 역설

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc() {
  return (
    <>
      <View>
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

Higher Order Component를 만들어 원할때

언제든지 쓰게할것이다

따라서 이름을 AnimateHoc라고 지어보았다.

 

그럼 박스의 크기를 일단 구해본다.

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout}> <-- 박스크기 측정
        { children }
      </View>
    </>
  )
}

 

이러면 쉽게 박스의 크기를 구할수있는데,

이것이 사용자에게 노출되면 안된다.

꼼수로 opacity로 투명도를 낮춰주자.

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

투명도를 낮추더라도 공간은 차지하니

아 아래에 다른 무언가가 있다면 이 HOC는

쓰기에 적합하지 않다. 다른 방법을 찾아야한다.

아니면 약간의 수정을

 

 

다음은, children이 서서히 나타나며 보이게하는 부분이다

(마치, Collapsible 처럼)

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true); <-- 굳이 init이라는 장치를 한번 더 적용한 이유는 잘못된 리렌더링을 방지하기 위함
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- 서서히 나타나야될 부분
        style={[{ height: 0 }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      { <-- 크기 측정을 위해서 opacity: 0 상태로 렌더링하다가 측정완료되면 렌더링 하지않음
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
          { children }
        </View>
      }
    </>
  )
}

 

이제는 완벽하다 생각했다

문제없이 동작할것이라 생각했는데

duration이 없기라도한듯

한번에 animated view가 나타났다

 

왜지, 아무리 구글링을해도

Docs를 살펴봐도

라이브러리 사용면에서는 문제는 없어보였다.

 

그래서 animated view에  backgroundColor를 넣어서

정상적으로 서서히 크기가 커지나 살펴보니

아주 정상적이었다!

다만, 그 내용물이 한번에 나타났을뿐!

박스는 천천히, 내용물은 한번에...

 

그래서overflow hidden을 설정해줌으로써 완벽히 정리되었다.

최종 코드

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true);
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- overflow 멈춰!!!
        style={[{ height: 0, overflow: 'hidden' }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      {
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}>
          { children }
        </View>
      }
    </>
  )
}

 

이제 이걸 활용한다면 이렇게 할수있을것이다.

// 기본 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc>
      <View>
        <Text>1. 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)

// 시간 조절 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc duration={ 1500 }>
      <View>
        <Text>1. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 천천히 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)
반응형