본문 바로가기

Ionic 3

[6] Ionic Camera 기능 구현


Ionic Framework & Firebase를 기용하여 현재까지 구현된 기능은 사용자 등록, 로그인, 사용자 리스트 출력 기능 입니다. 이번 포스팅에서는 Cordova의 Camera Plugin을 설치하여 갤러리에서 사진을 선택하고 Firebase Storage에 업로드하여 페이지에 출력해보는 임의의 사용자 프로필 화면을 구현해 보도록 하겠습니다.




Camera Plugin 추가

(1) Plugin, Package 설치

사진 찍기 및 업로드 기능을 구현하기 위해 Camera Plugin과 Package를 추가해야 합니다.


$ionic plugin add cordova-plugin-camera --save

$npm install --save @ionic-native/camera

(2) 프로젝트에 Module 추가

최상위 app.module.ts에 설치한 Camera를 import하고 providers에 등록해줍니다. 그리고 이후 생성할 ProfilePage 또한 import 합니다.

src/app/app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//Angular Module
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ErrorHandler, NgModule } from '@angular/core';
 
//Ionic Module
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { Camera } from '@ionic-native/camera';
 
//Firebase Module
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireModule } from 'angularfire2';
 
//Pages Component
import { MyApp } from './app.component';
import { TabsPage } from '../pages/tabs/tabs';
import { HomePage } from '../pages/home/home';
import { ListPage } from '../pages/list/list';
import { LoginPage } from '../pages/login/login';
import { RegisterPage } from '../pages/register/register';
import { ProfilePage } from '../pages/profile/profile';
 
//Firebase auth key
import { FIREBASE_CONFIG } from './config/app.firebase.config';
 
//Service
import { AuthProvider } from '../providers/auth/auth';
 
@NgModule({
  declarations: [
    MyApp,
    TabsPage,
    HomePage,
    ListPage,
    LoginPage,
    RegisterPage,
    ProfilePage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    AngularFireDatabaseModule,
    AngularFireAuthModule,
    AngularFireModule.initializeApp(FIREBASE_CONFIG),
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    TabsPage,
    HomePage,
    ListPage,
    LoginPage,
    RegisterPage,
    ProfilePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: ErrorHandler, useClass: IonicErrorHandler },
    Camera,
    AuthProvider
  ]
})
export class AppModule {
}
 
cs



Profile 페이지에 Camera 기능 구현

Camera 기능이 구현될 Profile 페이지를 생성하고 로직을 구현합니다. 갤러리를 통해 다수의 사진의 등록하여 Multiple로 이미지를 업로드 하도록 하는 로직을 구현하도록 하겠습니다.

$ionic g page profile


src/app/pages/profile/profile.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Profile</ion-title>
  </ion-navbar>
</ion-header>
 
 
<ion-content padding class="card-background-page">
    <button ion-button full (click)='takePhoto()'><ion-icon name="camera"></ion-icon>&nbsp;&nbsp;Take Photo</button>
    <button ion-button full (click)='choosePhoto()'><ion-icon name="ios-images"></ion-icon>&nbsp;&nbsp;Select Photo</button>
    <ion-grid>
        <ion-row>
            <ion-col col-6 *ngFor="let photo of photos; let id = index">
                <ion-card class="releative">
                  <ion-icon ios="ios-add-circle" md="md-add-circle" class="deleteIcon" (click)="deletePhoto(id)"></ion-icon>
                  <img [src]="domSanitizer.bypassSecurityTrustUrl(photo)" *ngIf="photo" />
                </ion-card>
            </ion-col>
        </ion-row>
    </ion-grid>
    <button ion-button full (click)='uploadPhoto()'><ion-icon name="ios-cloud-upload-outline"></ion-icon>&nbsp;&nbsp;Upload Photo</button>
    <ion-grid>
        <ion-row>{{processString}}</ion-row>
    </ion-grid>
</ion-content>
 
 
cs


src/app/pages/profile/profile.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { Component } from '@angular/core';
import { AlertController, ToastController } from 'ionic-angular';
import { DomSanitizer } from '@angular/platform-browser';
import { Camera, CameraOptions } from '@ionic-native/camera';
 
import firebase from 'firebase';
 
@Component({
  selector: 'page-profile',
  templateUrl: 'profile.html',
})
export class ProfilePage {
  public photos: any;
  public base64Image: string;
  public processString: string;
  private urlPrefix: string = 'file://';
  private base64Prefix: string = 'data:image/jpeg;base64,';
  private sendImage: string;
  private imageblob: any;
 
  constructor(public domSanitizer: DomSanitizer,
    private camera: Camera,
    private alertCtrl: AlertController,
    private toastCtrl: ToastController) { }
 
  ngOnInit() {
    this.photos = [];
  }
 
  deletePhoto(index) {
    let confirm = this.alertCtrl.create({
      title: 'Sure you want to delete this photo? There is NO undo!',
      message: '',
      buttons: [
        {
          text: 'No',
          handler: () => {
            console.log('Disagree clicked');
          }
        }, {
          text: 'Yes',
          handler: () => {
            console.log('Agree clicked');
            this.photos.splice(index, 1);
            //return true;
          }
        }
      ]
    });
    confirm.present();
  }
 
  choosePhoto() {
    const options: CameraOptions = {
      quality: 50,
      sourceType: this.camera.PictureSourceType.PHOTOLIBRARY,
      allowEdit: true,
      destinationType: this.camera.DestinationType.DATA_URL,
      encodingType: this.camera.EncodingType.JPEG,
      mediaType: this.camera.MediaType.PICTURE,
      saveToPhotoAlbum: false
    };
    this.camera.getPicture(options).then((imageData) => {
      // const safeUrl: any = this.domSanitizer.bypassSecurityTrustUrl(this.base64Prefix + imageData);
      const safeUrl: any = this.base64Prefix + imageData;
      this.sendImage = safeUrl;
      this.photos.push(safeUrl);
      this.photos.reverse();
    }, (err) => {
      console.log(err);
    });
  }
 
  takePhoto() {
    const options: CameraOptions = {
      quality: 50,
      // destinationType: this.camera.DestinationType.FILE_URI,
      destinationType: this.camera.DestinationType.DATA_URL,
      encodingType: this.camera.EncodingType.JPEG,
      mediaType: this.camera.MediaType.PICTURE
    };
    this.camera.getPicture(options).then(
      (imageData) => {
        // this.base64Image = this.urlPrefix + imageData;
        const safeUrl: any = this.base64Prefix + imageData;
        this.photos.push(safeUrl);
        this.photos.reverse();
      },
      (err) => {
        console.log(err);
      });
  }
 
  uploadPhoto() {
    console.log('uploadPhoto click!');
    let storageRef = firebase.storage().ref();
    // let storageRef = firebase.storage().refFromURL('gs://moblile-base.appspot.com');
    this.processString = '';
    this.processString += 'get storage!';
    // this.showToast('get storage!');
    // Create a timestamp as filename
    const filename = Math.floor(Date.now() / 1000);
    console.log('storageRef =>', storageRef.name);
    // Create a reference to 'images/todays-date.jpg'
    const imagesRef = storageRef.child('images');
    console.log('imagesRef =>', imagesRef);
    const imageRef = storageRef.child(`images/${filename}.jpg`);
    // this.showToast('Create a reference!');
    this.processString += 'Create a reference!';
    console.log('Create a reference!');
    const metadata = {
      contentType: 'image/jpeg',
    };
    // imageRef.put(this.b64toBlob(this.imageblob, 'image/jpeg'), metadata)
    imageRef.putString(this.sendImage, firebase.storage.StringFormat.DATA_URL).then(
      (snapshot: any) => {
        // this.showToast('upload success!');
        this.processString += 'upload success!';
      },
      (err) => {
        console.log(err);
        this.processString += 'upload fail => ' + JSON.stringify(err);
        // this.showToast('upload fail => '+ JSON.stringify(err));
      });
  }
 
  private b64toBlob(b64Data, contentType, sliceSize?) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;
 
    let byteCharacters = atob(b64Data);
    let byteArrays = [];
 
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      let slice = byteCharacters.slice(offset, offset + sliceSize);
 
      let byteNumbers = new Array(slice.length);
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
 
      let byteArray = new Uint8Array(byteNumbers);
 
      byteArrays.push(byteArray);
    }
 
    let blob = new Blob(byteArrays, { type: contentType });
    return blob;
  }
 
  private showToast(text: string) {
    let toast = this.toastCtrl.create({
      message: text,
      duration: 5000,
      position: 'top'
    });
 
    toast.onDidDismiss(() => {
      console.log('Dismissed toast');
    });
 
    toast.present();
  }
}
 
cs


app.comonent.ts 에서 로그인 했을 경우에 Side menu에서 로그인 페이지 대신 Profile 패이지가 생성되도록 코드를 추가합니다.


src/app/app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { AngularFireAuth } from 'angularfire2/auth';
 
import { TabsPage } from '../pages/tabs/tabs';
import { LoginPage } from '../pages/login/login';
import { ProfilePage } from '../pages/profile/profile';
 
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  @ViewChild(Nav) nav: Nav;
 
  rootPage: any = TabsPage;
 
  pages: Array<{ title: string, component: any }>;
 
  constructor(public platform: Platform,
    private afAuth: AngularFireAuth,
    public statusBar: StatusBar,
    public splashScreen: SplashScreen) {
    this.initializeApp();
    // used for an example of ngFor and navigation
    this.pages = [
      { title: 'login', component: LoginPage }
    ];
  }
  
  initializeApp() {
    this.afAuth.authState.subscribe(auth => {
      if (auth) {
        this.rootPage = TabsPage;
        this.pages = [
          { title: 'Profile', component: ProfilePage }
        ];
      } else {
        this.rootPage = LoginPage;
      }
    });
 
    this.platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      this.statusBar.styleDefault();
      this.splashScreen.hide();
    });
  }
 
  openPage(page) {
    // Reset the content nav to have just this page
    // we wouldn't want the back button to show in this scenario
    this.nav.setRoot(page.component);
  }
}
 
cs



Ionic View를 이용한 테스트

Ionic View를 사용하면 자신의 Native 환경에서 어플리케이션에 대한 베타 테스트를 진행 할 수 있습니다.


Ionic view download, docs url: https://docs.ionic.io/tools/view/ 



Upload된 프로젝트를 Sync하여 어플리케이션을 실행시킨다.

Loading 화면


Profile 페이지 확인




Camera기능 확인

갤러리를 통해 다수의 사진을 등록한후 Upload하게 되면 FIrebase Console의 Storage탭에서 업로드된 사진에 대한 이름, 유형, 이미지, 생성 시간, 용량 등의 다양한 정보를 확인 할 수 있습니다.