Ksaar documentation
Se connecter
  • 👋Bienvenue sur Ksaar !
  • 👾Les concepts de base
    • ✨Créer une App
    • ✋L'Espace Personnel
    • 🎨Éditer le contenu
  • LES ÉLÉMENTS
    • 🔠Les éléments
    • 🧑‍🎨 Éléments contenus basiques
    • ✔️ Éléments champs
    • { } Les Variables
    • 📈Éléments données
    • 🖇️Éléments liaisons
      • ↔️Liaison synchronisée
    • ⏭️Éléments matrices
    • 💯Les Calculs
      • 📆Calculs sur des dates
      • 🔢Calculs conditionnels
      • 🔗Calculs de liaison
    • ➗Les Formules
  • Les workflows
    • 🗂️Le Workflow
      • 🚀Workflow public
    • 📋Les Pages
    • 💥Les Actions
      • ✉️Envoi d'e-mails et de SMS
      • 🧭Redirection
      • 📄Créer un document
      • ⏭️Exporter une fiche
      • 🔄Mettre à jour les champs et liaisons
      • 💱Convertir des fichiers
      • 🚻Créer un groupe
      • 👯‍♂️Ajouter au(x) groupe(s)
      • 📝Signature électronique
      • ☎️Call API
      • 🧠Ksaar AI
      • 🔃Boucle
      • ⏺️Créer un enregistrement
      • 🗑️Supprimer un enregistrement
      • ✔️Activer un utilisateur
      • 🚫Désactiver un utilisateur
      • 📲Créer un QR code
      • 👤Créer un utilisateur
      • ⏺️Sauvegarder l'enregistrement
      • 🗓️Créer un ICS
    • ⬆️Importer des données
    • ⬇️Exporter des données
    • 👀Les Suivis
    • 📑Les fiches : Modifier/afficher vos données
    • ⚙️Les tables
  • Les utilisateurs
    • 🤵‍♀️Créer un Persona
    • 🤵‍♂️Éditer un Persona
    • 👥Groupes
    • 👤Utilisateurs
    • ➕Ajouter un utilisateur
    • 🔐Délégation de l'administration
  • L'affichage des données
    • 👀Visualiser les données
    • 🔲Tableaux
    • 🗓️Plannings
    • ⬜Kanban
    • 📉Dataviz
    • ⚫Listes
    • 📍Cartes
    • 📆Planning de ressources
    • 🕙Planning de réservation de créneaux
  • Design
    • 🔡Customisation des polices
  • 🎨Gestion des styles d'une application
  • Le multi-environnement
    • 🌳Le multi-environnement
      • 🤓L'essentiel du multi-environnement
      • ⏯️Utiliser le multi-environnement
      • 📤Mettre en production
      • 🕑L'historique de versions
  • ALLER PLUS LOIN
    • 🔗Les Liaisons
    • ☝️Les Conditions
    • 🎬Les Scénarios Conditionnels
    • 🪆Les Poupées Russes
    • 🚀Les Automations
      • ⏺️Quand un enregistrement est créé
      • 💱Quand un enregistrement est modifié
      • ⏱️À une date prévue
      • 🌐Quand un webhook est reçu
      • 📩Quand un e-mail est reçu
    • 🔎Vérifier si un enregistrement est déjà existant
    • 📱Créer une app iOS
    • 💾Sauvegarder ses données
  • CONNEXIONS
    • 📧Connexion à un serveur SMTP
      • Paramétrer un SMTP Google / Gmail
      • Paramétrer un SMTP Outlook
    • 🌐API Ksaar
    • 🧰Make
      • 📥Remplir un Google Sheets avec des enregistrements Ksaar
      • 🗓️Synchroniser un agenda Google avec des enregistrements Ksaar
    • 🤖Zapier
  • EN SAVOIR PLUS
    • 📗Le dico Ksaar
    • ❓FAQ
Propulsé par GitBook
Sur cette page
  • Créer le projet
  • Créer une WebView
  • Ajouter une TopBar et BottomBar
  • Ajouter la navigation
  • Ajouter du CSS personnalisé
  • Récupérer les informations d’un utilisateur
  • Déclencher une action native à iOS depuis la WebView

Cet article vous a-t-il été utile ?

  1. ALLER PLUS LOIN

Créer une app iOS

PrécédentVérifier si un enregistrement est déjà existantSuivantSauvegarder ses données

Dernière mise à jour il y a 2 mois

Cet article vous a-t-il été utile ?

Dans cette page, nous allons voir comment créer une application mobile iOS à partir d'une application Ksaar, prête à être publiée sur l'app store.

Cette documentation est plus technique que les autres, mais tout ce qui est présenté ici peut-être reproduit simplement en faisant des copier-coller des morceaux de codes présents dans chaque étape.

Créer le projet

Dans le dossier ‘Assets’, on place l'icône qu'aura notre application dans 'AppIcon', puis on ajoute l'image de son logo qu'on renomme 'Logo' pour pouvoir l'utiliser plus tard.

Créer une WebView

Dans le fichier ContentView, qui va contenir les éléments principaux de notre application, on créé notre structure WebView classique avec les fonctions makeUIView, qui permet d'initialiser la WebView, et updateUIView, qui permet de la mettre à jour.

On place notre WebView dans notre ContentView avec une url de test dans un premier temps.

On obtient alors une simple app avec uniquement notre WebView qui affiche le site choisi.

import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {    
    var url: String
    
    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        guard let url = URL(string: self.url) else {
            return
        }
        let request = URLRequest(url: url)
        webView.load(request)
    }

}

struct ContentView: View {
    var body: some View {
        WebView(url: "https://www.example.com")
    }
}

Ajouter une TopBar et BottomBar

On va maintenant ajouter une TopBar et une BottomBar pour pouvoir naviguer entre plusieurs pages de notre application Ksaar.

Pour cela, on créé d'abord un nouveau fichier swift ‘Config’ qui va contenir tous les paramètres de notre application.

Dedans, on créé une structure ‘Page’ qui contient une icône et une url et qui définit toutes les icônes que l'on aura dans nos barres de navigations et les urls des pages de notre application Ksaar vers lesquelles ces icônes renvoient.

On ajoute une première ’settingsPage’, issue de cette structure, qui sera dans notre TopBar, puis toutes les pages qui seront dans notre BottomBar, on peut en mettre le nombre que l’on souhaite.

Puis, on définit la couleur de navigation qu'on souhaite utiliser pour signaler la page en cours.

import SwiftUI
import Foundation

struct Page {
    let icon: String
    let url: String
}

let settingsPage = Page(icon: "gearshape", url:"<à compléter>")

let pages = [
        Page(icon: "calendar", url: "<à compléter>"),
        Page(icon: "person", url: "<à compléter>"),
        Page(icon: "house", url: "<à compléter>"),
        Page(icon: "folder", url: "<à compléter>"),
        Page(icon: "doc.text", url: "<à compléter>"),
    ]

extension Color {
    init(hex: String) {
        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
        var int: UInt64 = 0
        Scanner(string: hex).scanHexInt64(&int)
        let a, r, g, b: UInt64
        switch hex.count {
        case 3: // RGB (12-bit)
            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
        case 6: // RGB (24-bit)
            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
        case 8: // ARGB (32-bit)
            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
        default:
            (a, r, g, b) = (255, 0, 0, 0)
        }
        self.init(
            .sRGB,
            red: Double(r) / 255,
            green: Double(g) / 255,
            blue:  Double(b) / 255,
            opacity: Double(a) / 255
        )
    }
}

let navigationColorHex = "29E0A6"

BottomBar

Au clic sur un bouton, on indique que c’est la page correspondante qui est sélectionnée, ce qui change la couleur du bouton.

struct BottomBar: View {
    @Binding var selectedPage: Int
    @Binding var showSettings: Bool
    
    var body: some View {
        HStack {
            ForEach(pages.indices, id: \.self) { i in
                Button(action: {
                    self.selectedPage = i
                    self.showSettings = false
                }) {
                    VStack {
                        Image(systemName: pages[i].icon)
                            .imageScale(.large)
                            .foregroundColor(selectedPage == i ? Color(hex: navigationColorHex) : Color(hex: "000000"))
                        Circle()
                            .fill(selectedPage == i ? Color(hex: navigationColorHex) : Color.clear)
                            .frame(width: 5, height: 5)
                    }
                }
                .frame(maxWidth: .infinity)
            }
        }
        .padding(.bottom, 10)
        .padding(.top, 8)
        .background(Color(hex: "FFFFFF"))
    }
}

TopBar

De la même manière, on créé notre TopBar avec le logo au milieu et une icône vers une page tout à droite, qui peut être une page utilisateur, une page de réglage ou autre.

struct TopBar: View {
    @Binding var selectedPage: Int
    @Binding var showSettings: Bool
    
    var body: some View {
        ZStack {
            HStack {
                Spacer()
                Image("Logo")
                    .resizable()
                    .scaledToFit()
                    .frame(height: 30)
                Spacer()
            }

            HStack {
                Spacer()
                Button(action: {
                    self.selectedPage = -1
                    self.showSettings = true
                }) {
                    Image(systemName: settingsPage.icon)
                        .imageScale(.large)
                        .foregroundColor(self.showSettings ? Color(hex: navigationColorHex) : Color(hex: "000000"))
                }
                .padding(.trailing, 20)
            }
        }
        .padding(.bottom, 8)
        .background(Color(hex: "FFFFFF"))
    }
}

On ajoute nos TopBar et BottomBar dans la ContentView.

struct ContentView: View {
    @State private var selectedPage = 0
    @State private var showSettings = false
    
    var body: some View {
        VStack(spacing: 0) {
            TopBar(selectedPage: $selectedPage, showSettings: $showSettings)
            WebView(url: "https://www.example.com")
            BottomBar(selectedPage: $selectedPage, showSettings: $showSettings)
        }
    }
}

Ajouter la navigation

On a maintenant nos barres de navigation, mais la page ne change pas encore sur la WebView.

Pour pouvoir faire la navigation, et que chaque icône redirige bien vers la page voulue, il suffit dans un premier temps de donner l’url correspondant dans la WebView à la place de notre url d’exemple.

On choisit la première page comme page par défaut.

struct ContentView: View {
    @State private var selectedPage = 0
    @State private var showSettings = false
    
    var body: some View {
        VStack(spacing: 0) {
            TopBar(selectedPage: $selectedPage, showSettings: $showSettings)
            WebView(url: self.showSettings ? settingsPage.url : (selectedPage >= 0 && selectedPage < pages.count) ? pages[selectedPage].url : "")
            BottomBar(selectedPage: $selectedPage, showSettings: $showSettings)
        }
    }
}

On a maintenant une navigation fonctionnelle depuis nos barres de navigation.

Si on veut aller plus loin, il nous reste à gérer le cas où l’on serait redirigé vers un autre lien directement depuis notre WebView et pas par les barres de navigation, il faut alors mettre à jour nos barres de navigation pour qu’une icône ne soit plus sélectionnée si on n'est plus sur la bonne url.

struct WebView: UIViewRepresentable {
    var url: String
    var coordinator: Coordinator
    
    class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, ObservableObject {
        
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            
        }
        
        var webView: WKWebView?

        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            if keyPath == "URL", let webView = object as? WKWebView {
                // Handle the URL change
                if let newURL = webView.url {
                    NotificationCenter.default.post(name: Notification.Name("WebViewURLChanged"), object: nil, userInfo: ["url": newURL.absoluteString])
                }
            }
        }

    }

    func makeCoordinator() -> Coordinator {
        return coordinator
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        
        webView.addObserver(context.coordinator, forKeyPath: "URL", options: .new, context: nil)
        
        return webView
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        guard let url = URL(string: self.url) else {
            return
        }
        let request = URLRequest(url: url)
        webView.load(request)
    }

}

Maintenant, on peut ajouter notre coordinator dans notre contentView et une fonction qui va mettre à jour les barres de navigation à la réception de la notification et donc à chaque changement d’url.

struct ContentView: View {
    @StateObject private var webViewCoordinator = WebView.Coordinator()
    @State private var selectedPage = 0
    @State private var showSettings = false
    
    var body: some View {
        VStack(spacing: 0) {
            TopBar(selectedPage: $selectedPage, showSettings: $showSettings)
            WebView(url: self.showSettings ? settingsPage.url : (selectedPage >= 0 && selectedPage < pages.count) ? pages[selectedPage].url : "", coordinator: webViewCoordinator)
            BottomBar(selectedPage: $selectedPage, showSettings: $showSettings)
        }
        .onReceive(NotificationCenter.default.publisher(for: Notification.Name("WebViewURLChanged")), perform: { notification in
            guard let url = notification.userInfo?["url"] as? String else { return }
            updateSelectedPageForUrl(url)
        })
    }
    
    func updateSelectedPageForUrl(_ url: String) {
        if let index = pages.firstIndex(where: { url.contains($0.url) }) {
            selectedPage = index
            showSettings = false
        } else if (url.contains(settingsPage.url)){
            selectedPage = -1
            showSettings = true
        } else {
            selectedPage = -1
            showSettings = false
        }
    }
}

Ajouter du CSS personnalisé

Maintenant que nous avons un Top et BottomBar fonctionnelles, on peut vouloir modifier le css de notre page Ksaar pour, par exemple, enlever le menu de navigation qui est par défaut sur mobile. On peut aussi vouloir enlever la scrollbar qui n’est pas très utile sur une application mobile.

Pour cela, il faut créer notre propre css, dans un nouveau fichier Design.js, que l’on va injecter dans la page :

var css = `
header {
  display:none !important;
}
::-webkit-scrollbar {
    display: none;
}
`;

var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet){
    style.styleSheet.cssText = css;
} else {
    style.appendChild(document.createTextNode(css));
}

document.getElementsByTagName('head')[0].appendChild(style);
func makeUIView(context: Context) -> WKWebView {
    let configuration = WKWebViewConfiguration()

    if let cssFilePath = Bundle.main.path(forResource: "Design", ofType: "js"),
       let cssFileContent = try? String(contentsOfFile: cssFilePath) {
        let cssScript = WKUserScript(source: cssFileContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        configuration.userContentController.addUserScript(cssScript)
    }
    
    let webView = WKWebView(frame: .zero, configuration: configuration)
    
    webView.addObserver(context.coordinator, forKeyPath: "URL", options: .new, context: nil)
    webView.navigationDelegate = context.coordinator
    
    return webView
}

Récupérer les informations d’un utilisateur

On peut vouloir récupérer des informations de la WebView directement dans notre application pour pouvoir les utiliser, comme par exemple le mail de l’utilisateur connecté.

Dans cet exemple, nous allons récupérer le mail de l'utilisateur pour afficher la TopBar et la BottomBar uniquement si l’utilisateur est connecté.

Pour cela, on ajoute du javascript dans la fonction WebView de notre classe Coordinator qui permet de récupérer l’email de l’utilisateur qui est dans le localStorage.

class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, ObservableObject {
    @Published var email: String = ""
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        webView.evaluateJavaScript("localStorage.getItem('userData');") { [weak self] (result, error) in
            guard let self = self, let userDataString = result as? String,
                  let userData = try? JSONSerialization.jsonObject(with: Data(userDataString.utf8), options: []) as? [String: Any],
                  let email = userData["email"] as? String else {
                return
            }

            self.email = email
        }
    }

    ///
}

On peut maintenant afficher les barres de navigation uniquement dans le cas où l’utilisateur est bien connecté. On définit alors une variable isUserLoggedIn qui se met à jour quand on reçoit un mail qui n'est pas vide, et on affiche les barres de navigation uniquement dans le cas où cette variable est vraie et donc que notre utilisateur est connecté.

@StateObject private var webViewCoordinator = WebView.Coordinator()
@State private var selectedPage = 0
@State private var showSettings = false
@State public var isUserLoggedIn = false

var body: some View {
    VStack(spacing: 0) {
        if isUserLoggedIn {
            TopBar(selectedPage: $selectedPage, showSettings: $showSettings)
        }
            
        WebView(url: self.showSettings ? settingsPage.url : (selectedPage >= 0 && selectedPage < pages.count) ? pages[selectedPage].url : "", coordinator: webViewCoordinator)
        .onReceive(webViewCoordinator.$email) { email in
            DispatchQueue.main.async {
                isUserLoggedIn = !email.isEmpty
            }
        }
        
        if isUserLoggedIn {
            BottomBar(selectedPage: $selectedPage, showSettings: $showSettings)
        }
    }
    .onReceive(NotificationCenter.default.publisher(for: Notification.Name("WebViewURLChanged")), perform: { notification in
        guard let url = notification.userInfo?["url"] as? String else { return }
        updateSelectedPageForUrl(url)
    })
}

Déclencher une action native à iOS depuis la WebView

Il est possible d’effectuer une action native à iOS depuis un élément intégré à la WebView comme par exemple un bouton. En combinant cela avec la récupération de l’email de l’utilisateur, il est possible par exemple d’ajouter des achats in-app à l’application et de débloquer certaines pages et fonctionnalités à partir de cela.

On va commencer par créer un nouveau fichier JavaScript ‘CustomButton.js’ où l’on va créer notre bouton personnalisé que l’on va pouvoir intégrer dans une page.

Vous pouvez personnaliser ce bouton comme vous le souhaitez, ici on lui donne un style similaire aux boutons par défaut sur Ksaar.

var customButton = document.createElement("button");

var span = document.createElement("span");
span.innerHTML = "Bouton";
customButton.style.marginBottom = "20px"
span.style.padding = "11px 30px";
span.style.borderRadius = "22px";
span.style.backgroundColor = "#29E0A6";
span.style.color = "white";
customButton.appendChild(span);

customButton.addEventListener("touchstart", function() {
    span.style.backgroundColor = "#4ce6b5";
});

customButton.addEventListener("touchend", function() {
    span.style.backgroundColor = "#29E0A6";
});

customButton.addEventListener("mousedown", function() {
    span.style.backgroundColor = "#4ce6b5";
});

customButton.addEventListener("mouseup", function() {
    span.style.backgroundColor = "#29E0A6";
});

var checkDivInterval = setInterval(function() {
    var customButtonDiv = document.getElementById("custom-button-div");
    if (customButtonDiv) {
        clearInterval(checkDivInterval);
        customButtonDiv.appendChild(customButton);
    }
}, 100);

setTimeout(function() {
    clearInterval(checkDivInterval);
}, 5000);

De la même manière que pour le css, on injecte notre bouton en javascript dans la fonction makeUIView :

if let jsFilePath = Bundle.main.path(forResource: "CustomButton", ofType: "js"),
   let jsFileContent = try? String(contentsOfFile: jsFilePath) {
    let userScript = WKUserScript(source: jsFileContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
    configuration.userContentController.addUserScript(userScript)
}

Pour ajouter le bouton n’importe où sur l’application Ksaar, il suffit d’ajouter un élément HTML avec le contenu suivant à l'endroit où vous souhaitez placer votre bouton, l'élément sera reconnu grâce à l'id et rempli avec le bouton qu'on a créé :

<div id="custom-button-div" style="text-align: center;"></div>

Pour effectuer une action à partir du bouton, on ajoute dans le javascript une fonction, qui se déclenche au clic sur le bouton, qui envoie un message qui permet d’être reçu et utilisé dans la partie native de notre application. On ajoute le mail de l'utilisateur dans ce message pour l'utiliser.

Dans le fichier CustomButton.js, on ajoute notre fonction au clic dans checkDivInterval :

var checkDivcustInterval = setInterval(function() {
    var customButtonDiv = document.getElementById("custom-button-div");
    if (customButtonDiv) {
        clearInterval(checkDivInterval);
        customButtonDiv.appendChild(customButton);
        
        customButton.addEventListener("click", function() {
            var userData = localStorage.getItem('userData');
            var email = JSON.parse(userData).email;
            webkit.messageHandlers.customButton.postMessage(email);
        });
    }
}, 100);
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, ObservableObject {
    @Published var email: String = ""
    @Published var showAlert: Bool = false
    @Published var alertMessage: String = ""
    
    func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        if message.name == "customButton", let alertEmail = message.body as? String {
            DispatchQueue.main.async {
                self.alertMessage = "Utilisateur : \(alertEmail)"
                self.showAlert = true
            }
        }
    }
    
    ///
}

Puis, on déclare notre userContentController dans makeUIView

func makeUIView(context: Context) -> WKWebView {
    let configuration = WKWebViewConfiguration()
    let userContentController = WKUserContentController()
    userContentController.add(coordinator, name: "customButton")
    configuration.userContentController = userContentController
    
    ///
}

Enfin, on créé notre alerte iOS et on la fait apparaître quand on reçoit le message du bouton, au niveau de notre WebView.

WebView(url: self.showSettings ? settingsPage.url : (selectedPage >= 0 && selectedPage < pages.count) ? pages[selectedPage].url : "", coordinator: webViewCoordinator)
.onReceive(webViewCoordinator.$email) { email in
    DispatchQueue.main.async {
        isUserLoggedIn = !email.isEmpty
    }
}
.alert(isPresented: $webViewCoordinator.showAlert) {
        Alert(title: Text("Message"), message: Text(webViewCoordinator.alertMessage), dismissButton: .default(Text("OK")))
}

On commence par créer un nouveau et on remplit les quelques informations nécessaires.

Pour définir avec son logo, il faut aller dans les réglages de notre application, puis dans l'onglet Info, il faut ensuite aller dans 'Custom iOS Target Properties' et rajouter une ligne sous 'Launch Screen' avec le nom 'Image Name' et y mettre le nom de notre fichier : 'Logo'.

On débute ce projet par la qui permet d'afficher une page internet sur notre application.

On ajoute aussi dans ce fichier une pour les couleurs.

Dans le fichier ContentView, en plaçant autant de boutons qu’on a définis de pages dans le fichier Config, chaque bouton ayant l’icône choisie.

Pour cela, on ajoute un coordinateur dans notre structure WebView qui permet de . On envoie alors une notification avec l’url actuelle dès que celle-ci change.

Pour , on ajoute une configuration dans notre fonction makeUIView qui permet de mettre du script personnalisé :

Dans cet exemple, nous allons voir comment déclencher une contenant l’email de l’utilisateur à partir d’un bouton directement intégré dans la WebView.

Pour de notre application mobile, on ajoute un dans notre classe Coordinator qui permet de faire le pont entre notre application et le javascript :

📱
projet d'app IOS sur Xcode
l'écran qui s'affiche au lancement de son application
création d'une simple WebView
extension qui permet d'utiliser des valeurs hexadécimales
on créé notre BottomBar
détecter les changements
injecter ce css dans notre page
alerte iOS native
réceptionner ce message issue du javascript et l'utiliser dans la partie native
userContentController
Paramétrage de l'écran de lancement
Première WebView
BottomBar
TopBar
BottomBar
Navigation avec redirection depuis la WebView
Bouton Custom intégré dans la WebView sur une page Ksaar
Alerte native intégré à l'application Ksaar