Interface Builder: vad är det, visuell design i Xcode och storyboards

Författare: IT Sectr Publicerad: 2026-02-12 Lästid: 15 min

Interface Builder — en visuell gränssnittsredigerare inbyggd i Xcode för iOS och macOS-utveckling. Gör det möjligt att skapa UI via drag-and-drop, konfigurera Auto Layout, koppla kod via IBOutlet och IBAction. Vi går igenom hur IB fungerar, skillnaderna mellan Storyboard och XIB och varför @IBDesignable behövs.

Huvudpunkter

  • Interface Builder — visuell redigerare i Xcode för UIKit-gränssnitt utan att skriva layoutkod
  • Storyboard beskriver flera skärmar och övergångar mellan dem; XIB — en komponent eller skärm
  • Auto Layout i IB sätter begränsningar via menyerna Pin, Align och Resolve Issues
  • IBOutlet och IBAction kopplar Swift-kod till UI-element via Ctrl+drag
  • @IBDesignable och @IBInspectable visar anpassade vyer direkt på IB-arbetsytan

Vad är Interface Builder?

Interface Builder — en komponent i Xcode som är avsedd för visuell design av användargränssnitt. Historien om IB började redan 1988 på företaget NeXT, långt innan iOS kom till. Stefan Pope utvecklade den första versionen för NeXTSTEP — operativsystemet som blev grunden för macOS och iOS. 1996 köpte Apple NeXT och integrerade Interface Builder i Xcode.

I moderna Xcode stöder Interface Builder tre filformat: Storyboard, XIB (Xcode Interface Builder) och XIB-filer för tabellceller och anpassade vyer. Vart och ett av dessa format lagrar en XML-beskrivning av UI-elementens hierarki, deras egenskaper, begränsningar och kopplingar till kod.

IB arbetar på UIKit-nivå: knappar, etiketter, textfält, tabeller, samlingar och begränsningar dras med musen till arbetsytan. Xcode kompilerar .storyboard- och .xib-filer till nib-arkiv (compiled Interface Builder) vid byggsteget, vilket minskar bundle-storleken och snabbar upp inläsningen.

Enligt Apple använder mer än 70% av iOS-projekten på UIKit Interface Builder i olika utvecklingsstadier. Trots SwiftUI:s framväxt förblir IB standarden för kommersiella applikationer som stöder iOS 12 och lägre, samt för komplexa anpassade gränssnitt som kräver finjustering av Auto Layout.

Hur Interface Builder kom till Xcode

Före Xcode 4 var Interface Builder en separat applikation som kördes parallellt med kodredigeraren. I Xcode 4 (2011) slog Apple ihop IB och kodredigeraren till en enda IDE. Detta gjorde det möjligt att växla mellan kod och layout utan att byta fönster, samt att se egenskapsändringar i realtid via panelen Attributes Inspector.

Xcode-versionÅrÄndringar i Interface Builder
Xcode 32008IB — separat applikation, stöd för iOS 2.0
Xcode 42011IB inbyggd i IDE, Storyboard introducerades
Xcode 52013Auto Layout med begränsningsmeny, skärmförhandsvisning
Xcode 62014Size Classes, @IBDesignable, Preview Assistant
Xcode 112019SwiftUI Canvas, IB förblir för UIKit
Xcode 152023SwiftUI Preview som huvudverktyg, IB legacy-läge

Med SwiftUI:s ankomst 2019 flyttade Apple fokus till deklarativ utveckling, men Interface Builder förblir inbyggt i Xcode för att stödja UIKit-projekt. Tusentals befintliga applikationer fortsätter att använda IB och Apple har inte meddelat att det tas bort.

Storyboard och XIB: IB-filformat

Interface Builder stöder två huvudformat: Storyboard (.storyboard) och XIB (.xib). Skillnaden mellan dem ligger i omfattning och användningsscenario.

Storyboard — en fil som innehåller hela applikationens scen: flera skärmar (UIViewController), övergångar mellan dem (segues), navigationskontroller, flikfält och alla UI-element. Storyboard laddas en gång vid start från Info.plist via nyckeln UIMainStoryboardFile (k). Det är bekvämt för att visualisera skärmflödet, men skapar problem vid merge-konflikter i git eftersom XML-beskrivningen av hela applikationen lagras i en enda fil.

XIB (förkortning för Xcode Interface Builder) — en fil för en enda komponent: en separat UIView, UITableViewCell, UICollectionViewCell eller en ViewController. XIB laddas på begäran via UINib(nibName:bundle:) (k) eller metoden Bundle.loadNibNamed (k). XIB-filer är lättare att merge:a, mer kompakta och laddas snabbare eftersom de inte innehåller en beskrivning av hela applikationen.

KriteriumStoryboardXIB
OmfattningFlera skärmar + övergångarEn skärm eller komponent
SeguesStöder (push, modal, unwind)Stöder inte
Merge i gitKomplicerad (en stor XML)Enkel (många små filer)
InladdningVid applikationsstartPå begäran (lat)
ÅteranvändningEndast via storyboard-referenserHög (celler, rubriker, vyer)
Apple-rekommendationRekommenderas inte för stora projektRekommenderas för komponenter

Från och med Xcode 11 rekommenderar Apple att använda XIB för enskilda komponenter och undvika monolitiska Storyboard. För navigering mellan skärmar föredras kodbaserad navigering via UIStoryboardSegue (k) manuellt eller koordinatorer.

XML-struktur i IB-filer

.storyboard- och .xib-filer lagrar XML i formatet Interface Builder Cocoa Touch XIB (dt). Exempel på förenklad struktur:

xml
<!-- XIB-fil med UIView och UILabel -->
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB"
          version="3.0">
  <objects>
    <view id="abc-123"
          userLabel="CustomHeaderView"
          contentMode="scaleToFill">
      <subviews>
        <label id="def-456"
               text="Titel"
               textColor="darkTextColor"
               fontDescription="title1"/>
      </subviews>
    </view>
  </objects>
</document>

Varje element har ett unikt id (an) genom vilket IB kopplar XML-noden till runtime-objektet. Vid kompilering konverterar Xcode XML till binärt nib-format (.nib), vilket minskar filstorleken med cirka 40%.

Auto Layout och Size Classes i Interface Builder

Auto Layout — ett system för att placera element på skärmen genom matematiska relationer (begränsningar). Interface Builder tillhandahåller ett visuellt gränssnitt för att skapa, redigera och felsöka begränsningar utan att skriva kod. Varje begränsning beskriver ett beroende: view.leading = superview.leading + 16 (k) eller view.width = 2 * otherView.height (k).

I IB skapas begränsningar via menyerna Pin (fästning av marginaler, bredd, höjd) och Align (justering till centrum, kanter, baseline). Panelen Size Inspector visar alla begränsningar för det valda elementet, deras prioriteter (required/high/low) och låter dig redigera multiplikatorer och konstanter.

IB stöder även UIStackView — en behållare som automatiskt hanterar placeringen av underordnade vyer. Det räcker att placera element i en stack view på arbetsytan så genererar IB automatiskt nödvändiga begränsningar. Detta snabbar upp layouten avsevärt jämfört med manuell placering av begränsningar.

Size Classes: anpassning till enheter

Size Classes — en abstraktion som grupperar enheter efter skärmbredd och höjd: Compact och Regular. Kombinationer (wC hR för iPhone i porträtt, wR hR för iPad) gör det möjligt att ställa in olika begränsningar och elementplacering för olika scenarier. I Interface Builder ändrar växling mellan size classes uppsättningen aktiva begränsningar på arbetsytan.

EnhetOrienteringWidth ClassHeight Class
iPhone (förutom Max/Plus)PorträttCompactRegular
iPhone (förutom Max/Plus)LandskapCompactCompact
iPhone Plus/MaxLandskapRegularCompact
iPadValfriRegularRegular
iPad Split View1/3 skärmCompactRegular

Exempel på begränsning med variation efter size class:

swift
import UIKit

class AdaptiveViewController: UIViewController {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var leadingConstraint: NSLayoutConstraint!

    private func updateConstraints() {
        let isRegular = traitCollection.horizontalSizeClass == .regular
        leadingConstraint.constant = isRegular ? 40 : 16
        titleLabel.font = isRegular
            ? UIFont.preferredFont(forTextStyle: .largeTitle)
            : UIFont.preferredFont(forTextStyle: .title1)
    }

    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)
        if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass {
            updateConstraints()
        }
    }
}

I koden ovan reagerar traitCollectionDidChange på förändring av size class och uppdaterar begränsningen och teckensnittet. Interface Builder låter dig ställa in standardvärden för varje size class via inspektorn och koden används för dynamiska scenarier som inte kan beskrivas statiskt.

IBOutlet, IBAction och koppling av kod till UI

Kopplingen mellan det visuella gränssnittet i Interface Builder och Swift/Objective-C-koden sker via två mekanismer: IBOutlet (Interface Builder Outlet) och IBAction (Interface Builder Action). Båda skapas genom att dra med nedtryckt Ctrl-tangent från IB-arbetsytan till kontrollfilen.

IBOutlet — en annotering som deklarerar en referens till ett UI-element. Xcode kopplar automatiskt till motsvarande objekt i nib-arkivet vid inladdning. Om anslutningen bryts (t.ex. elementet byter namn) kraschar applikationen med felet NSUnknownKeyException (k). IBOutlet markeras som weak (k) eftersom nib äger objektet och kontrollern bara är en observatör.

IBAction — en metod som anropas vid en UI-elementhändelse: knapptryckning, textändring, omkopplare. IB kopplar UIControlEvent (k) till metoden via addTarget:action:forControlEvents: (k). I koden ser IBAction ut som en vanlig metod med returtypen IBAction (dt).

swift
import UIKit

final class LoginViewController: UIViewController {

    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var loginButton: UIButton!
    @IBOutlet weak var spinner: UIActivityIndicatorView!

    @IBAction private func loginButtonTapped(_ sender: UIButton) {
        guard let email = emailTextField.text, !email.isEmpty,
              let password = passwordTextField.text, !password.isEmpty
        else {
            showAlert(message: "Fyll i alla fält")
            return
        }
        loginButton.isEnabled = false
        spinner.startAnimating()
        performLogin(email: email, password: password)
    }

    private func performLogin(email: String, password: String) {
        /// API-anrop via URLSession
        let request = LoginRequest(email: email, password: password)
        APIClient.shared.login(request) { [weak self] result in
            DispatchQueue.main.async {
                guard let self else { return }
                self.spinner.stopAnimating()
                self.loginButton.isEnabled = true
                switch result {
                case .success:
                    self.navigateToMainScreen()
                case .failure(let error):
                    self.showAlert(message: error.localizedDescription)
                }
            }
        }
    }

    private func showAlert(message: String) {
        let alert = UIAlertController(
            title: "Fel",
            message: message,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

I exemplet visas en standardkoppling: IBOutlet för textfält, knapp och spinner, IBAction för att hantera knapptryckning. Alla dessa kopplingar upprättas i Interface Builder via Ctrl+drag. Om kopplingen inte är konfigurerad kommer IBOutlet att vara nil (v) vid körning, vilket orsakar krasch vid åtkomst — därför deklareras IBOutlet som weak var (k s) med implicit unwrap.

@IBDesignable och @IBInspectable: anpassade komponenter

@IBDesignable — en Swift-annotering som gör det möjligt att visa en anpassad UIView direkt på Interface Builder-arbetsytan i realtid. Utvecklaren ser resultatet av kodändringar utan att starta applikationen. @IBInspectable — en annotering för egenskaper som lägger till dem i panelen Attributes Inspector i IB, där värden kan ändras interaktivt.

Dessa annoteringar är särskilt användbara när man skapar bibliotek med UI-komponenter: anpassade knappar, inmatningsfält med mask, animerade indikatorer. IBDesignable använder prepareForInterfaceBuilder() (fn) för separat kompilering av byggkoden som inte påverkar applikationens huvudbinär.

swift
import UIKit

@IBDesignable
final class GradientButton: UIButton {

    @IBInspectable var startColor: UIColor = .systemBlue {
        didSet { updateGradient() }
    }

    @IBInspectable var endColor: UIColor = .systemPurple {
        didSet { updateGradient() }
    }

    @IBInspectable var cornerRadius: CGFloat = 12 {
        didSet {
            layer.cornerRadius = cornerRadius
            layer.masksToBounds = true
        }
    }

    private let gradientLayer = CAGradientLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupGradient()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupGradient()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
    }

    private func setupGradient() {
        layer.insertSublayer(gradientLayer, at: 0)
        updateGradient()
    }

    private func updateGradient() {
        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setupGradient()
    }
}

I koden ovan är GradientButton en IBDesignable-komponent med IBInspectable-egenskaperna startColor (v), endColor (v) och cornerRadius (v). När du drar en UIView till IB-arbetsytan och ändrar klassen till GradientButton i Identity Inspector, visas en knapp med gradient på arbetsytan i realtid. Alla IBInspectable-egenskaper visas i panelen Attributes Inspector till höger.

Viktigt: @IBDesignable kompilerar koden helt för visning i IB, därför bör nätverksförfrågningar eller långa operationer inte utföras inuti det. För åtskillnad används #if TARGET_INTERFACE_BUILDER (k) — villkorlig kompilering som utesluter kod som inte är avsedd för IB.

Livscykel för IB-filer vid kompilering

Processen för att transformera Interface Builder-filer från nib-skapande till visning på skärmen omfattar flera steg. Att förstå denna cykel hjälper till att diagnostisera problem med IB.

Vid byggsteget kör Xcode verktyget ibtool (k, fn) — ett kommandoradsverktyg för att kompilera .storyboard- och .xib-filer till binärt nib-format. ibtool utför också validering: kontrollerar begränsningarnas korrekthet, förekomsten av alla klasser, typerna av IBOutlet/IBAction-anslutningar. Valideringsfel visas i Xcodes Issue Navigator.

Det slutliga .nib-arkivet placeras i applikationens bundle i mappen .nib (s). Nib-filens storlek är betydligt mindre än den ursprungliga XML:en: det binära formatet använder en optimerad representation med ersättning av strängar med token och komprimering av numeriska värden. Typisk komprimering är 50–60% av den ursprungliga XML-storleken.

Vid körning laddas nib via UINib(nibName:bundle:) (k) eller automatiskt via UIStoryboard.instantiateViewController(withIdentifier:) (k). Inladdningsprocessen omfattar:

  • Deserialisering av den binära nib till en Objective-C/Swift-objektgraf
  • Skapande av instanser av alla UI-element från arkivet
  • Återställning av IBOutlet- och IBAction-anslutningar (outletCollection för grupper)
  • Tillämpning av Auto Layout-begränsningar från arkivet med hänsyn till size class
  • Anrop av awakeFromNib() (fn) för varje objekt — startpunkt för konfiguration efter inladdning

Metoden awakeFromNib() (fn) anropas efter att alla IBOutlet har ställts in, men före den första layoutSubviews. Detta är bekvämt för initial konfiguration: inställning av avrundningar, tillägg av skuggor, lokalisering av text. Alla IBOutlet är redan garanterat icke-nil i awakeFromNib.

Interface Builder mot SwiftUI Preview

Med lanseringen av SwiftUI 2019 fick iOS-utvecklare ett alternativ till Interface Builder — ett deklarativt ramverk med Canvas Preview i realtid. Låt oss gå igenom de viktigaste skillnaderna mellan de två tillvägagångssätten.

Interface Builder genererar en XML-beskrivning som kompileras till nib. Gränssnittet skapas visuellt, koden ansvarar bara för logiken. IB kräver en lägre inträdesbarriär för designers utan programmeringskunskaper men är komplicerat för kodgranskning (XML-ändringar syns inte i diffen).

SwiftUI Preview — helt kodbaserad utveckling. Gränssnittet beskrivs i Swift, förhandsvisningen uppdateras vid varje sparande. Inget XML, inget nib, ingen risk för brutna IBOutlet-anslutningar. SwiftUI Preview fungerar snabbare än IB eftersom det inte kräver kompilering av en separat fil.

KriteriumInterface Builder (UIKit)SwiftUI Preview
FilformatXML (.storyboard / .xib) → binär nibSwift-kod (ingen mellanfil)
FörhandsvisningIB-arbetsyta med fördröjning vid komplexa vyerCanvas Preview i realtid
Stöd för iOS-versioneriOS 2.0+ (alla versioner)iOS 13+
Merge i gitProblematisk (en XML-fil)Enkel (vanlig Swift-kod)
Dynamisk dataVia IBOutlet + kod@State (k), @Observable (k)
Anpassade vyer@IBDesignable (kompilering)SwiftUI View med PreviewProvider
PrestandaSnabb nib-inladdningOmedelbar Swift-kompilering

I praktiken beror valet mellan IB och SwiftUI Preview på projektets krav. Interface Builder är oumbärligt för UIKit-applikationer med stöd för äldre iOS och för kommersiella projekt där designers arbetar i Xcode utan Swift-kunskaper. SwiftUI är att föredra för nya projekt inriktade på iOS 17+, där utvecklingshastighet och reaktivitet är viktiga.

Apple planerar inte att ta bort Interface Builder från Xcode. Dessutom har företaget i Xcode 16 förbättrat prestandan för IB-arbetsytan och lagt till stöd för SwiftUI-komponenter via UIViewRepresentable Bridge. Det förväntas att IB kommer att stödjas minst till 2030.

Bästa praxis för att arbeta med Interface Builder

Mångårig erfarenhet av iOS-utveckling har format en uppsättning rekommendationer som minskar antalet problem vid användning av Interface Builder i kommersiella projekt.

Använd XIB istället för Storyboard för återanvändbara komponenter. Varje anpassad tabellcell, rubrik eller sidfot ska vara i en separat XIB. Detta underlättar merge, snabbar upp inladdning och gör det möjligt att återanvända komponenter mellan projekt via Swift Package Manager eller CocoaPods.

Konfigurera Storyboard References för att dela upp stora storyboards i moduler. Istället för en enda Main.storyboard med 100 skärmar, skapa en storyboard per modul (Auth, Profile, Feed) och koppla dem via Storyboard Reference. Detta minskar ibtools kompileringstid och förenklar teamarbete.

Undvik IBOutlet-anslutningar till File's Owner (k) utan kontroll. Varje anslutning ska vara weak (k) och valfri (implicitly unwrapped optional är bra bara i playground). När du byter namn på en IBOutlet i vyn uppdaterar Xcode automatiskt anslutningen, men vid manuell redigering av XML är det lätt att göra fel.

  • Kontrollera alltid Show Connection Panel (k) efter redigering av en IB-fil — röda indikatorer visar brutna anslutningar
  • Använd User Defined Runtime Attributes (k) för att ställa in egenskaper utan kod: layer.cornerRadius, layer.borderWidth, tintColor
  • Gruppera begränsningar i IB efter syfte: begränsningar för storlekar, begränsningar för marginaler, begränsningar för proportioner
  • Tilldela Identifier (k) till varje begränsning i Size Inspector — hjälper vid felsökning av konflikter
  • Lägg inte affärslogik i awakeFromNib — endast UI-konfiguration. Logik i viewDidLoad eller separata tjänster
swift
import UIKit

final class ProfileHeaderView: UIView {

    @IBOutlet weak var avatarImageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var bioLabel: UILabel!
    @IBOutlet weak var editButton: UIButton!

    override func awakeFromNib() {
        super.awakeFromNib()
        avatarImageView.layer.cornerRadius = avatarImageView.bounds.width / 2
        avatarImageView.layer.masksToBounds = true
        nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)
        bioLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
    }

    func configure(with profile: UserProfile) {
        nameLabel.text = profile.fullName
        bioLabel.text = profile.bio
        /// Ladda avatar via SDWebImage eller Kingfisher
    }

    static func instantiateFromNib() -> ProfileHeaderView {
        let nib = UINib(nibName: String(describing: self), bundle: nil)
        return nib.instantiate(withOwner: nil).first as! ProfileHeaderView
    }
}

I exemplet visas en bästa praxis för XIB-vyer: den statiska metoden instantiateFromNib (fn) laddar vyn från XIB med samma namn som klassen. Metoden awakeFromNib (fn) konfigurerar UI (avrundningar, teckensnitt) och metoden configure(with:) (fn) tar emot en datamodell för ifyllning. Uppdelning av ansvar förenklar testning och återanvändning.

Vanliga frågor

Hur skiljer sig Interface Builder från SwiftUI Preview?

Interface Builder — visuell redigerare för UIKit med Storyboard/XIB-format som fungerar via drag-and-drop. SwiftUI Preview — deklarativ förhandsvisning i realtid där gränssnittet beskrivs med Swift-kod. Båda verktygen är inbyggda i Xcode, men IB genererar XML medan SwiftUI kompilerar Swift direkt. IB stöder iOS 2.0+, SwiftUI — iOS 13+.

Kan Interface Builder användas med SwiftUI?

Nej, Interface Builder är inte direkt kompatibelt med SwiftUI. SwiftUI använder sin egen deklarativa syntax och Canvas Preview. UIKit-projekt skapade via IB kan dock integreras i SwiftUI via UIViewRepresentable och SwiftUI-vyer kan bäddas in i UIKit via UIHostingController. Detta möjliggör gradvis migrering från IB till SwiftUI.

Vad är @IBDesignable och @IBInspectable?

@IBDesignable — en Swift-annotering som visar en anpassad UIView direkt i Interface Builder i realtid utan att starta applikationen. @IBInspectable — en annotering för egenskaper som lägger till dem i panelen Attributes Inspector i IB. Båda annoteringarna snabbar upp utvecklingen av anpassade UI-komponenter: ändra bara en egenskap i inspektorn — ändringen syns omedelbart på arbetsytan.

Hur fungerar Auto Layout i Interface Builder?

Auto Layout i Interface Builder sätter begränsningar via menyerna Pin (marginaler, bredd, höjd) och Align (centrering, baseline). Varje begränsning är en matematisk relation mellan vyer. IB visar fel med röda linjer och konflikter med gula varningar. Size Classes i IB gör det möjligt att ställa in olika begränsningar för olika enheter och orienteringar utan att skriva kod.

Hur kopplar man kod till Interface Builder via IBOutlet och IBAction?

IBOutlet — en annotering för en referens till ett UI-element från koden (t.ex. @IBOutlet weak var label: UILabel!). IBAction — en annotering för en metod som anropas vid en händelse (t.ex. @IBAction func buttonTapped(_ sender: UIButton)). Kopplingen skapas via Ctrl+drag från IB-arbetsytan till kontrollfilen. Xcode genererar automatiskt kopplingskoden när musen släpps.

Sammanfattning

  • Interface Builder — visuell redigerare i Xcode för UIKit med historia från 1988 (NeXTSTEP)
  • Storyboard är lämpligt för prototypframställning, XIB — för återanvändbara komponenter och produktionsprojekt
  • Auto Layout och Size Classes i IB gör det möjligt att skapa adaptiva gränssnitt utan kod
  • IBOutlet och IBAction kopplar kod till UI via Ctrl+drag med automatisk generering av Swift-egenskaper
  • @IBDesignable och @IBInspectable snabbar upp utveckling av anpassade vyer med förhandsvisning i IB
  • SwiftUI Preview ersätter IB för nya projekt, men IB förblir standard för UIKit-äldre kod
  • Bästa praxis: XIB istället för Storyboard, weak IBOutlet, identifiering av begränsningar, separation av awakeFromNib och konfiguration

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också