iOS 14에서 고주용 식별자(IDFA)에 액세스하려는 앱의 경우 앱 추적 투명성(ATT) 프레임워크라는 메시지를 통해 사용자에게 액세스 권한을 요청해야 합니다.
애드몹 사이트에서 아래와 같이 2가지 알림이 표시되는데 해당 건을 처리하는 방법입니다.
[권한 요청 화면]
[React-Native 모듈 버전]
react : "16.11.0"
react-native : "0.62.2"
react-native-admob : "2.0.0-beta.6"
1. Info.plist 수정
(1) SKAdNetworkItems 추가
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array
(2) NSUserTrackingUsageDescription 추가
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
2. Podfile 수정
(1) <리액트 네이티브 프로젝트>/ios/Podfile.lock에서 Google-Mobile-Ads-SDK 버전 7.64.0 이상인지 확인합니다.
(2) <리액트 네이티브 프로젝트>/ios/Podfile에 Google-Mobile-Ads-SDK 버전을 7.69.0으로 지정한다.
(Google-Mobile-Ads-SDK 8.x.x에서는 react-native-admob 2.0.0-beta.6에서 컴파일 오류발생합니다.)
pod 'Google-Mobile-Ads-SDK','~> 7.69.0'
3. 추적 투명성 대화상자를 위한 react-native-tracking-transparency 모듈 설치
npm install react-native-tracking-transparency
cd ios
pod install
4. 앱 화면 소스
(1) 배너 전역 변수 선언
state={
...
banner : null,
}
(2) componentWillMount에서 추적 투명성 대화상자 요청
componentWillMount() {
requestTrackingPermission().then((status) => {
this.state.banner = <AdMobBanner
adSize="smartBannerPortrait"
adUnitID={ADMOB_BANNER_ID}
testDevices={[AdMobBanner.simulatorId]}/>;
this.setState(this.state.banner);
});
}
(3) 앱 화면에 배너 표시
render() {
return (
<View style={{flex: 1, backgroundColor:'#eeeeee'}}>
...
{this.state.banner}
</View>
);
}
[전체소스]
import React, { Component } from 'react';
import {View, Text, Switch, FlatList, TouchableOpacity, Image, Alert, AsyncStorage, AppState} from 'react-native';
import styles, {KEY_BOOKMARK, ADMOB_BANNER_ID,} from '../Common';
import { AdMobBanner, } from 'react-native-admob';
import { requestTrackingPermission, getTrackingStatus, } from 'react-native-tracking-transparency';
export default class BookmarkScreen extends Component{
state={
bookmarkList:[],
appState: AppState.currentState,
banner : null,
}
componentWillMount() {
requestTrackingPermission().then((status) => {
this.state.banner = <AdMobBanner
adSize="smartBannerPortrait"
adUnitID={ADMOB_BANNER_ID}
testDevices={[AdMobBanner.simulatorId]}/>;
this.setState(this.state.banner);
});
}
componentDidMount() {
this.search();
}
render() {
return (
<View style={{flex: 1, backgroundColor:'#eeeeee'}}>
<FlatList style={styles.round_full_box}
data={this.state.bookmarkList}
renderItem={({item}) => (
<TouchableOpacity style={styles.round_card_box} onPress={() => this.onClickItem(item)} onLongPress={() => this.onLongClickItem(item)}>
<View style={{flexDirection: 'row', flex: 1, alignItems:'center'}}>
<Text style={{flex: 1, }}>
{item.type}
</Text>
<TouchableOpacity onPress={() => this.onClickUp(item)}>
<Image source={require('../images/arrow_up.png')} style={{width:20, height:20, margin:5}} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.onClickDown(item)}>
<Image source={require('../images/arrow_down.png')} style={{width:20, height:20, margin:5}} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.onClickRemove(item)}>
<Image source={require('../images/close.png')} style={{width:20, height:20, margin:5}} />
</TouchableOpacity>
</View>
<View style={{flex: 1, backgroundColor:'#aaaaaa', height:1, marginTop:5, marginBottom:5}}/>
<View style={{flexDirection: 'row', flex: 1, alignItems:'center'}}>
<Image source={require('../images/dot.png')} style={{width:10, height:10, margin:5}}/>
<Text style={{flex: 1, fontSize:20, color:'#336699'}}>
{item.name}
</Text>
<Text>
{item.direction}
</Text>
</View>
<View style={{flexDirection: 'row', flex: 1, alignItems:'center'}}>
<Text style={{flex: 1, marginLeft:20, fontSize:18}}>
{item.no}
</Text>
<Text>
{item.time}
</Text>
</View>
</TouchableOpacity>
)}/>
{this.state.banner}
</View>
);
}
onClickItem(item){
if(item.type == '정류소'){
this.props.navigation.navigate('BUSSTOPDETAIL', {id:item.id, name:item.name, busstopNo:item.no, gpsX:item.gpsX, gpsY:item.gpsY});
}else if(item.type == '버스'){
this.props.navigation.navigate('BUSLINE', {id:item.id, name:item.name, type:item.no, direction:item.direction, time:item.time});
}
}
onClickRemove(item){
Alert.alert(
item.name + '(' + item.no + ')',
'삭제하시겠습니까?',
[
{text: '아니요', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
{text: '예', onPress: () => {
let idx = this.state.bookmarkList.indexOf(item);
this.state.bookmarkList.splice(idx, 1);
this.setState(this.state.bookmarkList);
this.state.bookmarkList.reverse();
let bookmarkListStr = JSON.stringify(this.state.bookmarkList);
AsyncStorage.setItem(KEY_BOOKMARK, bookmarkListStr, () => {
this.search();
});
}},
],
{ cancelable: false }
);
}
onClickUp(item){
let idx = this.state.bookmarkList.indexOf(item);
if(idx == 0) return;
this.state.bookmarkList.splice(idx, 1);
this.state.bookmarkList.splice(idx - 1, 0, item);
this.state.bookmarkList.reverse();
let bookmarkListStr = JSON.stringify(this.state.bookmarkList);
AsyncStorage.setItem(KEY_BOOKMARK, bookmarkListStr, () => {
this.search();
});
}
onClickDown(item){
let idx = this.state.bookmarkList.indexOf(item);
if(idx == this.state.bookmarkList.length - 1) return;
this.state.bookmarkList.splice(idx, 1);
this.state.bookmarkList.splice(idx + 1, 0, item);
this.state.bookmarkList.reverse();
let bookmarkListStr = JSON.stringify(this.state.bookmarkList);
AsyncStorage.setItem(KEY_BOOKMARK, bookmarkListStr, () => {
this.search();
});
}
search(){
AsyncStorage.getItem(KEY_BOOKMARK, (err, value )=>{
if(err == null){
this.state.bookmarkList = JSON.parse(value);
if(this.state.bookmarkList != null){
this.state.bookmarkList.reverse();
this.setState(this.state.bookmarkList);
}
}
});
}
}
※ iOS 앱을 심사신청하면 아래와 같이 NSUserTrackingUsageDescription 추가로 앱이 수집하는 개인정보를 수정한다.
※ 앱이 수집하는 개인정보에서 광고데이터에 "추적 목적으로 사용됨"을 선택하면 됩니다.
'React-Native' 카테고리의 다른 글
React-Native Execution failed for task ':app:mergeDexDebug' (0) | 2021.03.28 |
---|---|
React-Native error: package android.support.annotation does not exist (0) | 2021.03.28 |
React-Native iOS 앱 심사 반려(구글 맵 관련) (0) | 2021.02.26 |
React-Native iOS 앱 구글맵 링크 (0) | 2021.02.25 |
React-Native iOS 애드몹(Admob) 적용 (0) | 2020.07.21 |
댓글