Web geliştirme dünyası çok geniştir ve her gün ortaya çıkan yeni teknolojilerin sürekli akışında kaybolmuş gibi hissetmek kolaydır. Bu yeni teknolojilerin çoğu JavaScript veya TypeScript kullanılarak oluşturulmuştur. Ancak bu makalede, doğrudan tarayıcınızın içinde yerel Swift kullanarak web geliştirmeyi size tanıtacağım ve bunun sizi etkileyeceğinden eminim.
Swift'in web sayfasında yerel olarak çalışması için önce WebAssembly bayt koduna derlenmesi gerekir, ardından JavaScript bu kodu sayfaya yükleyebilir. Özel araç zincirini kullanmamız ve yardımcı dosyalar oluşturmamız gerektiğinden tüm derleme süreci biraz çetrefilli; bu nedenle yardımcı CLI araçları mevcut: Carton ve Webber.
SwiftWasm topluluğu, orijinal Swift araç zincirini yamalayarak Swift'in WebAssembly'de derlenmesini mümkün kılmak için muazzam miktarda çalışma yaptı. Her gün orijinal takım zincirindeki değişiklikleri otomatik olarak alarak ve testler başarısız olursa çatallarını düzelterek takım zincirini güncellerler. Hedefleri resmi alet zincirinin bir parçası olmak ve bunun yakın gelecekte gerçekleşmesini umuyorlar.
Carton , SwiftWasm topluluğu tarafından yapılmıştır ve size SwiftUI kullanarak web siteleri yazma yeteneği veren bir çerçeve olan Tokamak projeleri için kullanılabilir.
Webber, SwifWeb projeleri için tasarlandı. SwifWeb, tüm HTML ve CSS standartlarının yanı sıra tüm web API'lerini sarması bakımından farklıdır.
Her ne kadar kod tutarlılığı için SwiftUI kullanarak web uygulamaları yazmayı tercih etseniz de bunun yanlış bir yaklaşım olduğuna inanıyorum çünkü web geliştirme doğası gereği farklıdır ve SwiftUI ile aynı şekilde ele alınamaz.
Bu yüzden size HTML, CSS ve web API'lerinin tüm gücünü doğrudan Swift'den kullanma olanağı veren, otomatik tamamlama ve dokümantasyon içeren güzel sözdizimini kullanan SwifWeb'i yarattım. Ve Webber aracını oluşturdum çünkü Carton, kendisi için yaratılmadığından SwifWeb uygulamalarını doğru şekilde derleyemiyor, hata ayıklayamıyor ve dağıtamıyor.
Adım Mikhail Isaev ve SwifWeb'in yazarıyım. Bu yazıda size SwifWeb'i kullanarak bir web sitesi oluşturmaya nasıl başlayacağınızı göstereceğim.
Swift'in kurulu olması gerekiyor, bunu yapmanın en kolay yolu:
Diğer durumlarda resmi web sitesindeki kurulum talimatlarına bakın
Uygulamalarınızı oluşturmanıza, hata ayıklamanıza ve dağıtmanıza yardımcı olmak için Webber'ı yarattım.
MacOS'ta HomeBrew ile kurulumu kolaydır ( web sitesinden yükleyin)
brew install swifweb/tap/webber
daha sonra en son sürüme güncellemek için çalıştırmanız yeterli
brew upgrade webber
Ubuntu veya Windows'ta (WSL2'de Ubuntu) Webber'ı manuel olarak kopyalayın ve derleyin
sudo apt-get install binaryen curl https://get.wasmer.io -sSfL | sh apt-get install npm cd /opt sudo git clone https://github.com/swifweb/webber cd webber sudo swift build -c release sudo ln -s /opt/webber/.build/release/Webber /usr/local/bin/webber
son sürüme güncellemek için daha sonra çalıştırın
cd /opt/webber sudo git pull sudo swift build -c release
ana dal her zaman kararlı kod içerir, bu nedenle güncellemeleri ondan almaktan çekinmeyin
Terminali açın ve çalıştırın
webber new
İnteraktif menüde pwa
veya spa
seçin ve proje adını girin.
Dizini yeni oluşturulan projeyle değiştirin ve webber serve
komutunu çalıştırın.
Bu komut, projenizi WebAssembly'de derleyecek, gerekli tüm dosyaları özel bir .webber
klasöründe paketleyecek ve varsayılan olarak 8888
bağlantı noktasını kullanarak projenizi tüm arayüzlerde sunmaya başlayacaktır.
webber serve
için ek argümanlar
Uygulama türü
Aşamalı Web Uygulaması için -t pwa
Tek Web Uygulaması için -t spa
Hizmet çalışanı hedefinin adı (genellikle PWA projesinde Service
olarak adlandırılır)
-s Service
Uygulama hedefinin adı (Varsayılan olarak App
)
-a App
Konsolda daha fazla bilgi yazdırın
-v
Webber sunucusunun bağlantı noktası (varsayılan 8888
)
-p 8080
Gerçek SSL gibi test etmek için -p 443
kullanın (izin verilen kendinden imzalı SSL ayarıyla)
Otomatik olarak başlatılacak hedef tarayıcı adı
--browser safari
veya --browser chrome
Hizmet çalışanlarının hatalarını ayıklamak için izin verilen kendinden imzalı SSL ayarına sahip ek tarayıcı örneği
--browser-self-signed
Gizli modda ek tarayıcı örneği
--browser-incognito
Uygulama Sources/App/App.swift
başlar
import Web @main class App: WebApp { @AppBuilder override var app: Configuration { Lifecycle.didFinishLaunching { app in app.registerServiceWorker("service") } Routes { Page { IndexPage() } Page("login") { LoginPage() } Page("article/:id") { ArticlePage() } Page("**") { NotFoundPage() } } MainStyle() } }
İOS benzeri bir şekilde çalışır:
didFinishLaunching
yeni başlatıldığında başlatılıyor
uygulama sona erdiğinde willTerminate
pencere etkin olmadığında willResignActive
pencere aktif olduğunda didBecomeActive
didEnterBackground
pencere arka plana geçtiğinde
willEnterForeground
pencere ön plana geçtiğinde
Buradaki en kullanışlı yöntem didFinishLaunching
çünkü uygulamayı yapılandırmak için harika bir yerdir. Gerçekten bir iOS uygulaması gibi hissettirdiğini görüyorsunuz! 😀
İşte app
yararlı kolaylık yöntemleri içerir:
registerServiceWorker(“serviceName“)
PWA hizmet çalışanını kaydetmek için çağrı
göreceli veya harici komut dosyası eklemek için addScript(“path/to/script.js“)
çağrısı
göreceli veya harici stil eklemek için addStylesheet(“path/to/style.css“)
çağrısı
addFont(“path/to/font.woff”, type:)
göreli veya harici yazı tipi eklemek için çağrı, isteğe bağlı olarak türü ayarlayın
addIcon(“path/to/icon“, type:color:)
simge ekleme çağrısı, isteğe bağlı olarak türü ve rengi ayarlayın
Ayrıca Autolayout , Bootstrap , Materialize vb. gibi ek kitaplıkların yapılandırılacağı yerdir.
Geçerli URL'ye göre uygun sayfayı göstermek için yönlendirme gereklidir.
Yönlendirmenin nasıl kullanılacağını anlamak için URL'nin ne olduğunu anlamalısınız.
https://website.com/hello/world - işte /hello/world yol
Gördüğünüz gibi başlangıçta App sınıfında tüm üst düzey rotaları bildirmemiz gerekiyor.
Üst düzey, bu rotalarda bildirilen sayfaların penceredeki tüm alanı kaplayacağı anlamına gelir.
Tamam, örneğin kök rota üç şekilde ayarlanabilir
Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }
Bence en güzeli sonuncusu 🙂
Giriş veya kayıt rotaları bu şekilde ayarlanabilir
Page("login") { LoginPage() } Page("registration") { RegistrationPage() }
Parametreyle ilgili rotalar
Page("article/:id") { ArticlePage() }
Yukarıdaki örnekte :id rotanın dinamik bir parçasıdır. Kendisiyle ilişkili makaleyi görüntülemek için bu tanımlayıcıyı ArticlePage sınıfından alabiliriz.
class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }
Yolda birden fazla parametreniz olabilir. Hepsini aynı şekilde geri alın.
Yoldaki bir sonraki ilginç şey, kullanımı da çok kolay olan sorgudur . Örneğin, arama text
ve age
sorgusu parametrelerine sahip olmasını bekleyen /search rotasını ele alalım.
https://website.com/search**?text=Alex&age=19** - son kısım sorgudur
Arama rotasını bildirmeniz yeterli
Page("search") { SearchPage() }
Ve SearchPage sınıfındaki sorgu verilerini şu şekilde alın
class SearchPage: PageController { struct Query: Decodable { let text: String? let age: Int? } override func didLoad(with req: PageRequest) { do { let query = try req.query.decode(Query.self) // use optional query.text and query.age // to query search results } catch { print("Can't decode query: \(error)") } } }
Bunun gibi belirli yol kısmındaki herhangi bir şeyi kabul eden rotayı bildirmek için *
komutunu da kullanabilirsiniz.
Page("foo", "*", "bar") { SearchPage() }
Yukarıdaki rota foo ve bar arasındaki her şeyi kabul edecektir, örneğin /foo/aaa/bar, /foo/bbb/bar, vb.
**
işaretiyle, belirli bir yoldaki diğer rotalarla eşleşmeyen her şeyi ele alacak özel bir tümünü kapsayan rota ayarlayabilirsiniz.
Global 404 rotası oluşturmak için kullanın
Page("**") { NotFoundPage() }
veya belirli bir yol için, örneğin kullanıcı bulunamadığında
Page("user", "**") { UserNotFoundPage() }
Yukarıda belirtilen rotalarla durumları netleştirelim
/user/1 - eğer /user/:id için bir rota varsa o zaman UserPage değerini döndürür. Aksi halde düşecek…
Kullanıcı BulunamadıSayfa
/user/1/hello - eğer /user/:id/hello için bir rota varsa o zaman UserNotFoundPage'e düşecektir
/something - /something için bir rota yoksa NotFoundPage'e düşecektir
Bir sonraki rota için sayfadaki içeriğin tamamını değil, yalnızca belirli blokları değiştirmek isteyebiliriz. FragmentRouter'ın kullanışlı olduğu yer burasıdır!
/user sayfasında sekmelerimizin olduğunu düşünelim. Her sekme bir alt rotadır ve alt rotadaki değişikliklere FragmentRouter kullanarak tepki vermek istiyoruz.
App sınıfında üst düzey rotayı bildirin
Page("user") { UserPage() }
Ve FragmentRouter'ı UserPage sınıfında ilan edin
class UserPage: PageController { @DOM override var body: DOM.Content { // NavBar is from Materialize library :) Navbar() .item("Profile") { self.changePath(to: "/user/profile") } .item("Friends") { self.changePath(to: "/user/friends") } FragmentRouter(self) .routes { Page("profile") { UserProfilePage() } Page("friends") { UserFriendsPage() } } } }
Yukarıdaki örnekte, FragmentRouter /user/profile ve /user/friends alt rotalarını yönetir ve bunu Navbar altında işler, böylece sayfa hiçbir zaman içeriğin tamamını yeniden yüklemez, yalnızca belirli parçaları yeniden yükler.
Aynı veya farklı alt rotalara sahip birden fazla parça da bildirilebilir ve hepsi birlikte sihir gibi çalışacaktır!
Btw FragmentRouter bir Div'dir ve onu arayarak yapılandırabilirsiniz.
FragmentRouter(self) .configure { div in // do anything you want with the div }
Geleneksel CSS dosyalarını kullanabilirsiniz, ancak aynı zamanda Swift'de yazılmış bir stil sayfasını kullanma gibi yeni, sihirli bir yeteneğe de sahipsiniz!
Swift kullanarak bir CSS kuralı bildirmek için Rule nesnesine sahibiz.
Yöntemlerini çağırarak bildirimsel olarak oluşturulabilir.
Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)
veya @resultBuilder kullanarak SwiftUI benzeri bir yol
Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }
Her iki yol da eşittir ancak ben yazdıktan hemen sonra otomatik tamamlama nedeniyle ilkini tercih ediyorum .
😀
MDN'de açıklanan tüm CSS yöntemleri mevcuttur.
Dahası, tarayıcı öneklerini otomatik olarak yönetir!
Ancak bazı özel durumlarda özel özelliği bu şekilde ayarlayabilirsiniz.
Rule(...selector...) .custom("customKey", "customValue")
Kuralın hangi unsurları etkileyeceğini belirlemek için bir seçici ayarlamamız gerekir. Seçiciyi veritabanındaki sorgu olarak görüyorum, ancak bu seçici sorgusunun bazı kısımlarını işaretçi olarak adlandırıyorum.
Bir işaretçi oluşturmanın en kolay yolu, onu ham dizeyi kullanarak başlatmaktır.
Pointer("a")
Ancak doğru hızlı yol, gerekli HTML etiketinde .pointer
bu şekilde çağırarak bunu oluşturmaktır.
H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId
Temel işaretçilerle ilgilidir, ancak aynı zamanda :hover
:first
:first-child
vb. gibi değiştiricileri de vardır.
H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover
Mevcut herhangi bir değiştiriciyi bildirebilirsiniz, hepsi mevcuttur.
Eğer bir şey eksikse onu eklemek için uzantı yapmaktan çekinmeyin!
Herkese eklemek için github'a çekme isteği göndermeyi unutmayın.
Ayrıca işaretçileri birleştirebilirsiniz
H1.class(.myClass) // h1.myClass H1.id(.myId) // h1#myId H1.id(.myId).disabled // h1#myId:disabled Div.pointer.inside(P.pointer) // div p Div.pointer.parent(P.pointer) // div > p Div.pointer.immediatedlyAfter(P.pointer) // Div + p P.pointer.precededBy(Ul.pointer) // p ~ ul
Kuraldaki seçici nasıl kullanılır?
Rule(Pointer("a")) // or Rule(A.pointer)
Kuralda birden fazla seçici nasıl kullanılır?
Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))
Aşağıdaki CSS kodunu üretir
a, h1#myId, div > p { }
Uygulamamız için koyu ve açık stilleri tanımlayalım, daha sonra bunlar arasında kolayca geçiş yapabileceğiz.
import Web @main class App: WebApp { enum Theme { case light, dark } @State var theme: Theme = .light @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... LightStyle().disabled($theme.map { $0 != .happy }) DarkStyle().disabled($theme.map { $0 != .sad }) } }
LightStyle ve DarkStyle ayrı dosyalarda veya örneğin App.swift'te bildirilebilir.
class LightStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.white) Rule(H1.pointer).color(.black) } } class DarkStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.black) Rule(H1.pointer).color(.white) } }
Ve sonra bir sayfanın kullanıcı arayüzünde bir yerde arayın
App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme
Ve ilgili stil sayfalarını etkinleştirecek veya devre dışı bırakacaktır! Çok hoş değil mi? 😎
Ancak stilleri CSS yerine Swift'de tanımlamanın daha zor olduğunu söyleyebilirsiniz, o zaman ne anlamı var?
Ana nokta tepkiselliktir! @State'i CSS özellikleriyle kullanabilir ve değerleri anında değiştirebiliriz!
Sadece bir göz atın, bazı reaktif özelliklere sahip bir sınıf oluşturabilir ve onu çalışma zamanında istediğimiz zaman değiştirebiliriz, böylece ekrandaki bu sınıfı kullanan herhangi bir öğe güncellenecektir! Birçok öğe için sınıf değiştirmekten çok daha etkilidir!
import Web @main class App: WebApp { @State var reactiveColor = Color.cyan @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... MainStyle() } } extension Class { static var somethingCool: Class { "somethingCool" } } class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) } }
Daha sonra kodun herhangi bir yerinden aramanız yeterli
App.current.reactiveColor = .yellow // or any color you want
ve Stil Sayfasındaki ve onu kullanan tüm öğelerdeki rengi güncelleyecektir 😜
Ayrıca bir Stil Sayfasına ham CSS eklemek de mümkündür
class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) """ /* Raw CSS goes here */ body { margin: 0; padding: 0; } """ } }
ham CSS dizelerini gerektiği kadar karıştırabilirsiniz
Yönlendirici her rotadaki sayfaları oluşturuyor. Page, PageController'dan miras alınan herhangi bir sınıftır.
PageController'da willLoad
didLoad
willUnload
didUnload
, kullanıcı arayüzü yöntemleri buildUI
ve body
gibi yaşam döngüsü yöntemleri ve HTML öğeleri için özellik sarmalayıcı değişkeni bulunur.
Teknik olarak PageController yalnızca bir Div'dir ve özelliklerini buildUI
yönteminde ayarlayabilirsiniz.
class IndexPage: PageController { // MARK: - Lifecycle override func willLoad(with req: PageRequest) { super.willLoad(with: req) } override func didLoad(with req: PageRequest) { super.didLoad(with: req) // set page title and metaDescription self.title = "My Index Page" self.metaDescription = "..." // also parse query and hash here } override func willUnload() { super.willUnload() } override func didUnload() { super.didUnload() } // MARK: - UI override func buildUI() { super.buildUI() // access any properties of the page's div here // eg self.backgroundcolor(.lightGrey) // optionally call body method here to add child HTML elements body { P("Hello world") } // or alternatively self.appendChild(P("Hello world")) } // the best place to declare any child HTML elements @DOM override var body: DOM.Content { H1("Hello world") P("Text under title") Button("Click me") { self.alert("Click!") print("button clicked") } } }
Sayfanız küçükse bu kadar kısa yoldan bile ilan edebilirsiniz.
PageController { page in H1("Hello world") P("Text under title") Button("Click me") { page.alert("Click!") print("button clicked") } } .backgroundcolor(.lightGrey) .onWillLoad { page in } .onDidLoad { page in } .onWillUnload { page in } .onDidUnload { page in }
Güzel ve özlü değil mi? 🥲
Bonus kolaylık yöntemleri
alert(message: String)
- doğrudan JS alert
yöntemi
changePath(to: String)
- URL yolunu değiştirme
Son olarak size HTML öğelerini nasıl(!) oluşturup kullanacağınızı anlatacağım!
Tüm HTML öğeleri, öznitelikleriyle birlikte Swift'te mevcuttur, tam liste örneğin MDN'dedir .
HTML öğelerinin yalnızca örnek bir kısa listesi:
SwifWeb kodu | HTML Kodu |
---|---|
| |
| |
| |
| |
| |
| |
Gördüğünüz gibi Swift'de herhangi bir HTML etiketine erişmek çok kolaydır çünkü girişler dışında hepsi aynı adla temsil edilir. Bunun nedeni, farklı girdi türlerinin farklı yöntemlere sahip olmasıdır ve ben bunları karıştırmak istemedim.
Basit Div
Div()
bunun gibi tüm niteliklerine ve stil özelliklerine erişebiliriz
Div().class(.myDivs) // <div class="myDivs"> .id(.myDiv) // <div id="myDiv"> .backgroundColor(.green) // <div style="background-color: green;"> .onClick { // adds `onClick` listener directly to the DOM element print("Clicked on div") } .attribute("key", "value") // <div key="value"> .attribute("boolKey", true, .trueFalse) // <div boolKey="true"> .attribute("boolKey", true, .yesNo) // <div boolKey="yes"> .attribute("checked", true, .keyAsValue) // <div checked="checked"> .attribute("muted", true, .keyWithoutValue) // <div muted> .custom("border", "2px solid red") // <div style="border: 2px solid red;">
Stili önceden tanımlamak veya çok sayıda önceden tanımlanmış alt öğe ve dışarıda mevcut bazı uygun yöntemler ile bileşik bir öğe oluşturmak veya didAddToDOM
ve didRemoveFromDOM
gibi yaşam döngüsü olaylarını elde etmek için alt sınıf HTML öğesi.
Sadece bir Div
olan ancak önceden tanımlanmış .divider
sınıfına sahip bir Divider
öğesi oluşturalım
public class Divider: Div { // it is very important to override the name // because otherwise it will be <divider> in HTML open class override var name: String { "\(Div.self)".lowercased() } required public init() { super.init() } // this method executes immediately after any init method public override func postInit() { super.postInit() // here we are adding `divider` class self.class(.divider) } }
Alt sınıflandırma yaparken süper yöntemleri çağırmak çok önemlidir.
Bu olmadan beklenmedik davranışlarla karşılaşabilirsiniz.
Öğe, PageController'ın DOM'sine veya HTML öğesine hemen veya daha sonra eklenebilir.
Derhal
Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }
Veya daha sonra lazy var
kullanarak
lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }
Böylece bir HTML öğesini önceden bildirebilir ve daha sonra istediğiniz zaman DOM'a ekleyebilirsiniz!
lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()
Herhangi bir HTML öğesinin, DOM'a eklenmesi durumunda üst öğeye erişim sağlayan isteğe bağlı bir denetleme özelliği vardır.
Div().superview?.backgroundColor(.red)
Genellikle öğeleri yalnızca belirli koşullarda göstermemiz gerekir; bunun için if/else
kullanalım.
lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() var myDiv4: Div? var showDiv2 = true Div { myDiv1 if showDiv2 { myDiv2 } else { myDiv3 } if let myDiv4 = myDiv4 { myDiv4 } else { P("Div 4 was nil") } }
Fakat reaktif değildir. showDiv2
false
olarak ayarlamaya çalışırsanız hiçbir şey olmuyor.
Reaktif örnek
lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() @State var showDiv2 = true Div { myDiv1 myDiv2.hidden($showDiv2.map { !$0 }) // shows myDiv2 if showDiv2 == true myDiv3.hidden($showDiv2.map { $0 }) // shows myDiv3 if showDiv2 == false }
Neden $showDiv2.map {…}
kullanmalıyız ?
Sırayla: çünkü SwiftUI değil. Kesinlikle.
Aşağıda @State
hakkında daha fazla bilgi bulabilirsiniz .
Ayrıca sayfaya veya HTML öğesine ham HTML eklemeniz gerekebilir ve bu kolayca mümkündür.
Div { """ <a href="https://google.com">Go to Google</a> """ }
let names = ["Bob", "John", "Annie"] Div { ForEach(names) { name in Div(name) } // or ForEach(names) { index, name in Div("\(index). \(name)") } // or with range ForEach(1...20) { index in Div() } // and even like this 20.times { Div().class(.shootingStar) } }
@State var names = ["Bob", "John", "Annie"] Div { ForEach($names) { name in Div(name) } // or with index ForEach($names) { index, name in Div("\(index). \(name)") } } Button("Change 1").onClick { // this will append new Div with name automatically self.names.append("George") } Button("Change 2").onClick { // this will replace and update Divs with names automatically self.names = ["Bob", "Peppa", "George"] }
Yukarıdaki örneklerle aynı, ancak BuilderFunction
da mevcut
Stylesheet { ForEach(1...20) { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } 20.times { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } }
Aşağıdaki örnekte bir delay
değeri gibi bazı değerleri yalnızca bir kez hesaplamak için ForEach
döngülerinde BuilderFunction
kullanabilirsiniz.
ForEach(1...20) { index in BuilderFunction(9999.asRandomMax()) { delay in CSSRule(Pointer(".shooting_star").nthChild("\(index)")) .custom("top", "calc(50% - (\(400.asRandomMax() - 200)px))") .custom("left", "calc(50% - (\(300.asRandomMax() + 300)px))") .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").before) .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").after) .animationDelay(delay.ms) } }
Aynı zamanda bir argüman olarak da işlev görebilir
BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 }
BuilderFunction HTML öğeleri için de kullanılabilir :)
@State
günümüzde bildirimsel programlama için en çok arzu edilen şeydir.
Yukarıda da söylediğim gibi: SwiftUI değil, dolayısıyla her şeyi takip eden ve yeniden çizen bir küresel durum makinesi yok. Ve HTML öğeleri geçici yapılar değil sınıflardır, yani bunlar gerçek nesnelerdir ve onlara doğrudan erişebilirsiniz. Çok daha iyi ve esnektir, tüm kontrol sizdedir.
Değişiklikleri tüm abonelere bildiren bir özellik sarmalayıcıdır.
enum Countries { case usa, australia, mexico } @State var selectedCounty: Countries = .usa $selectedCounty.listen { print("country changed") } $selectedCounty.listen { newValue in print("country changed to \(newValue)") } $selectedCounty.listen { oldValue, newValue in print("country changed from \(oldValue) to \(newValue)") }
Basit metin örneği
@State var text = "Hello world!" H1($text) // whenever text changes it updates inner-text in H1 InputText($text) // while user is typing text it updates $text which updates H1
Basit sayı örneği
@State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div
Basit boole örneği
@State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div
Haritalama örneği
@State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })
İki eyaletin haritalanması
@State var one = true @State var two = true Div().display($one.and($two).map { one, two in // returns .block if both one and two are true one && two ? .block : .none })
İkiden fazla eyaletin haritalanması
@State var one = true @State var two = true @State var three = 15 Div().display($one.and($two).map { one, two in // returns true if both one and two are true one && two }.and($three).map { oneTwo, three in // here oneTwo is a result of the previous mapping // returns .block if oneTwo is true and three is 15 oneTwo && three == 15 ? .block : .none })
Tüm HTML ve CSS özellikleri @State
değerlerini işleyebilir
Div gibi somut elemanlara bazı kullanışlı yöntemler ekleyebilirsiniz.
extension Div { func makeItBeautiful() {} }
Veya ebeveyn class
biliyorsanız öğe grupları.
Az sayıda ebeveyn sınıfı vardır.
BaseActiveStringElement
- a
, h1
vb. gibi dizeyle başlatılabilen öğeler içindir.
BaseContentElement
- div
, ul
vb. gibi içinde içerik bulundurabilen tüm öğeler içindir.
BaseElement
- tüm öğeler içindir
Yani tüm elemanların uzantısı bu şekilde yazılabilir
extension BaseElement { func doSomething() {} }
Renk sınıfı renklerden sorumludur. Önceden tanımlanmış HTML renkleri vardır, ancak kendi renklerinize de sahip olabilirsiniz.
extension Color { var myColor1: Color { .hex(0xf1f1f1) } // which is 0xF1F1F1 var myColor2: Color { .hsl(60, 60, 60) } // which is hsl(60, 60, 60) var myColor3: Color { .hsla(60, 60, 60, 0.8) } // which is hsla(60, 60, 60, 0.8) var myColor4: Color { .rgb(60, 60, 60) } // which is rgb(60, 60, 60) var myColor5: Color { .rgba(60, 60, 60, 0.8) } // which is rgba(60, 60, 60, 0.8) }
Daha sonra H1(“Text“).color(.myColor1)
gibi kullanın.
extension Class { var my: Class { "my" } }
Sonra bunu Div().class(.my)
gibi kullanın
extension Id { var myId: Id { "my" } }
Daha sonra Div().id(.my)
gibi kullanın
window
nesnesi tamamen sarılmıştır ve App.current.window
değişkeni aracılığıyla erişilebilir.
Tam referans MDN'de mevcuttur.
Aşağıda kısa bir genel bakış yapalım
Bunu App.swift
Lifecycle
veya doğrudan bu şekilde dinleyebilirsiniz.
App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }
veya istediğiniz zaman istediğiniz yerde okuyun
if App.current.window.isInForeground { // do somethign }
veya HTML öğesiyle tepki verin
Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })
Ön Plan bayrağıyla aynıdır ancak App.current.window.isActive
aracılığıyla erişilebilir
Bir kullanıcının pencerenin içinde hâlâ etkileşimde olup olmadığını tespit eder.
Ön Plan bayrağıyla aynıdır ancak App.current.window.isOnline
aracılığıyla erişilebilir
Bir kullanıcının hala internete erişimi olup olmadığını tespit eder.
Ön Plan bayrağıyla aynıdır ancak App.current.window.isDark
aracılığıyla erişilebilir
Bir kullanıcının tarayıcısının veya işletim sisteminin karanlık modda olup olmadığını tespit eder.
Kaydırma çubukları da dahil olmak üzere pencerenin içerik alanının (görüntü alanı) boyutu
App.current.window.innerSize
, içindeki width
ve height
değerleri içindeki Size nesnesidir.
@State
değişkeni olarak da mevcuttur.
Araç çubukları/kaydırma çubukları da dahil olmak üzere tarayıcı penceresinin boyutu.
App.current.window.outerSize
, içindeki width
ve height
değerleri içindeki Size nesnesidir.
@State
değişkeni olarak da mevcuttur.
Geçerli pencerenin oluşturulduğu ekranın özelliklerini denetlemek için özel nesne. App.current.window.screen
aracılığıyla kullanılabilir.
En ilginç özellik genellikle pixelRatio
.
Kullanıcı tarafından ziyaret edilen URL'leri içerir (tarayıcı penceresi içinde).
App.current.window.history
veya sadece History.shared
aracılığıyla edinilebilir.
@State
değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz.
App.current.window.$history.listen { history in // read history properties }
Basit değişken olarak da erişilebilir
History.shared.length // size of the history stack History.shared.back() // to go back in history stack History.shared.forward() // to go forward in history stack History.shared.go(offset:) // going to specific index in history stack
Daha fazla ayrıntıyı MDN'de bulabilirsiniz.
Geçerli URL ile ilgili bilgileri içerir.
App.current.window.location
veya yalnızca Location.shared
aracılığıyla kullanılabilir.
@State
değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz.
Örneğin yönlendirici bu şekilde çalışır.
App.current.window.$location.listen { location in // read location properties }
Basit bir değişken olarak da erişilebilir
Location.shared.href // also $href Location.shared.host // also $host Location.shared.port // also $port Location.shared.pathname // also $pathname Location.shared.search // also $search Location.shared.hash // also $hash
Daha fazla ayrıntıyı MDN'de bulabilirsiniz.
Tarayıcı hakkında bilgi içerir.
App.current.window.navigator
veya sadece Navigator.shared
aracılığıyla edinilebilir
En ilginç özellikler genellikle userAgent
platform
language
cookieEnabled
.
Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Son kullanma tarihi olmayan verileri saklar.
App.current.window.localStorage
veya yalnızca LocalStorage.shared
olarak mevcuttur.
// You can save any value that can be represented in JavaScript LocalStorage.shared.set("key", "value") // saves String LocalStorage.shared.set("key", 123) // saves Int LocalStorage.shared.set("key", 0.8) // saves Double LocalStorage.shared.set("key", ["key":"value"]) // saves Dictionary LocalStorage.shared.set("key", ["v1", "v2"]) // saves Array // Getting values back LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.integer(forKey: "key") // returns Int? LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.value(forKey: "key") // returns JSValue? // Removing item LocalStorage.shared.removeItem(forKey: "key") // Removing all items LocalStorage.shared.clear()
Değişiklikleri izleme
LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }
Tüm öğelerin kaldırılmasını takip etme
LocalStorage.onClear { print("LocalStorage: all items has been removed") }
Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Yalnızca bir oturuma ait verileri depolar.
App.current.window.sessionStorage
veya yalnızca SessionStorage.shared
olarak mevcuttur.
API, yukarıda açıklanan LocalStorage ile tamamen aynıdır.
Tarayıcıya yüklenen herhangi bir web sayfasını temsil eder ve web sayfasının içeriğine giriş noktası görevi görür.
App.current.window.document
aracılığıyla edinilebilir.
App.current.window.document.title // also $title App.current.window.document.metaDescription // also $metaDescription App.current.window.document.head // <head> element App.current.window.document.body // <body> element App.current.window.documentquerySelector("#my") // returns BaseElement? App.current.window.document.querySelectorAll(".my") // returns [BaseElement]
Klasik yerelleştirme otomatiktir ve kullanıcının sistem diline bağlıdır
H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))
Ekrandaki yerelleştirilmiş dizeleri anında değiştirmek istiyorsanız (sayfayı yeniden yüklemeden)
Mevcut dili arayarak değiştirebilirsiniz.
Localization.current = .es
Kullanıcının dilini çerezlerde veya yerel depolamada bir yere kaydettiyseniz, bunu uygulama başlatılırken ayarlamanız gerekir.
Lifecycle.didFinishLaunching { Localization.current = .es }
H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))
H1(Localization.currentState.map { "Curent language: \($0.rawValue)" }) H2(LString(.en("English string"), .es("Hilo Español"))) Button("change lang").onClick { Localization.current = Localization.current.rawValue.contains("en") ? .es : .en }
import FetchAPI Fetch("https://jsonplaceholder.typicode.com/todos/1") { switch $0 { case .failure: break case .success(let response): print("response.code: \(response.status)") print("response.statusText: \(response.statusText)") print("response.ok: \(response.ok)") print("response.redirected: \(response.redirected)") print("response.headers: \(response.headers.dictionary)") struct Todo: Decodable { let id, userId: Int let title: String let completed: Bool } response.json(as: Todo.self) { switch $0 { case .failure(let error): break case .success(let todo): print("decoded todo: \(todo)") } } } }
import XMLHttpRequest XMLHttpRequest() .open(method: "GET", url: "https://jsonplaceholder.typicode.com/todos/1") .onAbort { print("XHR onAbort") }.onLoad { print("XHR onLoad") }.onError { print("XHR onError") }.onTimeout { print("XHR onTimeout") }.onProgress{ progress in print("XHR onProgress") }.onLoadEnd { print("XHR onLoadEnd") }.onLoadStart { print("XHR onLoadStart") }.onReadyStateChange { readyState in print("XHR onReadyStateChange") } .send()
import WebSocket let webSocket = WebSocket("wss://echo.websocket.org").onOpen { print("ws connected") }.onClose { (closeEvent: CloseEvent) in print("ws disconnected code: \(closeEvent.code) reason: \(closeEvent.reason)") }.onError { print("ws error") }.onMessage { message in print("ws message: \(message)") switch message.data { case .arrayBuffer(let arrayBuffer): break case .blob(let blob): break case .text(let text): break case .unknown(let jsValue): break } } Dispatch.asyncAfter(2) { // send as simple string webSocket.send("Hello from SwifWeb") // send as Blob webSocket.send(Blob("Hello from SwifWeb")) }
Basit print(“Hello world“)
JavaScript'teki console.log('Hello world')
ile eşdeğerdir
Konsol yöntemleri de sevgiyle sarılmış ❤️
Console.dir(...) Console.error(...) Console.warning(...) Console.clear()
Canlı önizlemenin çalışmasını sağlamak için istediğiniz her dosyada WebPreview sınıfını bildirin.
class IndexPage: PageController {} class Welcome_Preview: WebPreview { @Preview override class var content: Preview.Content { Language.en Title("Initial page") Size(640, 480) // add here as many elements as needed IndexPage() } }
Lütfen depo sayfasındaki talimatları okuyun. Zor ama tamamen işe yarayan bir çözüm 😎
VSCode içindeki Uzantılar'a gidin ve Webber'ı arayın.
Kurulduktan sonra Cmd+Shift+P
tuşlarına basın (veya Linux/Windows'ta Ctrl+Shift+P
)
Webber Live Preview
bulun ve başlatın.
Sağ tarafta canlı önizleme penceresini göreceksiniz ve WebPreview sınıfını içeren dosyayı her kaydettiğinizde yenilenir.
SwifWeb'in temeli olan JavaScriptKit aracılığıyla edinilebilir.
Resmi depoda nasıl yapılacağını okuyun.
Projenin içine css
, js
, png
, jpg
ve diğer statik kaynakları ekleyebilirsiniz.
Ancak bunları hata ayıklama sırasında veya son sürüm dosyalarında kullanılabilir hale getirmek için hepsini Package.swift'te bu şekilde bildirmeniz gerekir.
.executableTarget(name: "App", dependencies: [ .product(name: "Web", package: "web") ], resources: [ .copy("css/*.css"), .copy("css"), .copy("images/*.jpg"), .copy("images/*.png"), .copy("images/*.svg"), .copy("images"), .copy("fonts/*.woff2"), .copy("fonts") ]),
Daha sonra bunlara örneğin şu şekilde erişebileceksiniz Img().src(“/images/logo.png“)
Webber'ı aşağıdaki şekilde başlatın
webber serve
sadece hızlı bir şekilde başlatmak için
webber serve -t pwa -s Service
PWA modunda başlatmak için
Hata ayıklama amacıyla konsolda daha fazla bilgi göstermek için -v
veya --verbose
-p 443
veya --port 443
webber sunucusunu varsayılan 8888 yerine 443 bağlantı noktasında başlatmak için
--browser chrome/safari
istenen tarayıcıyı otomatik olarak açmak için, varsayılan olarak herhangi bir tarayıcıyı açmaz
--browser-self-signed
hizmet çalışanlarının yerel hatalarını ayıklamak için gerekli, aksi halde çalışmazlar
--browser-incognito
gizli modda ek tarayıcı örneğini açmak için, yalnızca Chrome ile çalışır
Uygulamanızı hata ayıklama modunda oluşturmak için, uygulamayı otomatik olarak Chrome'da açın ve herhangi bir dosyayı değiştirdiğinizde tarayıcıyı otomatik olarak yenileyin, uygulamayı bu şekilde başlatın
SPA için
webber serve --browser chrome
gerçek PWA testi için
webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito
İlk yükleme sürecini iyileştirmek isteyebilirsiniz.
Bunun için proje içindeki .webber/entrypoint/dev
klasörünü açın ve index.html
dosyasını düzenleyin.
Çok kullanışlı dinleyicilere sahip başlangıç HTML kodunu içerir: WASMLoadingStarted
WASMLoadingStartedWithoutProgress
WASMLoadingProgress
WASMLoadingError
.
Bu kodu, özel stilinizi uygulamak istediğiniz şekilde düzenlemekte özgürsünüz 🔥
Yeni uygulamayı tamamladığınızda, aynısını .webber/entrypoint/release
klasörüne kaydetmeyi unutmayın.
PWA için webber release
veya webber release -t pwa -s Service
çalıştırmanız yeterlidir.
Daha sonra derlenmiş dosyaları .webber/release
klasöründen alın ve sunucunuza yükleyin.
Dosyalarınızı herhangi bir statik hostinge yükleyebilirsiniz.
Hosting, wasm dosyaları için doğru İçerik Türünü sağlamalıdır!
Evet, wasm dosyaları için doğru Content-Type: application/wasm
başlığına sahip olmak çok önemlidir, aksi halde ne yazık ki tarayıcı WebAssembly uygulamanızı yükleyemeyecektir.
Örneğin GithubPages, wasm dosyaları için doğru İçerik Türünü sağlamadığından ne yazık ki üzerinde WebAssembly sitelerini barındırmak imkansızdır.
Nginx ile kendi sunucunuzu kullanıyorsanız /etc/nginx/mime.types
açın ve application/wasm wasm;
kayıt. Eğer evet ise o zaman gitmeye hazırsınız!
Umarım bugün sizi şaşırtmışımdır ve en azından SwifWeb'i deneyecek ve en fazla bir sonraki büyük web projeniz için kullanmaya başlayacaksınız!
Lütfen herhangi bir SwifWeb kütüphanesine katkıda bulunmaktan ve ayrıca hepsine ⭐️ yıldız eklemekten çekinmeyin!
Discord'da büyük bir destek bulabileceğiniz, küçük eğitimler okuyabileceğiniz ve gelecek güncellemelerden ilk önce haberdar olabileceğiniz harika bir SwiftStream topluluğum var! Seni aramızda görmek harika olurdu!
Bu sadece başlangıç; SwifWeb hakkında daha fazla makale için bizi takip etmeye devam edin!
Arkadaşlarına söyle!