نحوه ساخت ویجت بارکد در React Native (قسمت دوم: iOS)
در این آموزش قصد داریم نحوه ساخت ویجت بارکد برای یک دستگاه iOS را بیاموزیم.
در قسمت قبل روی اندروید و جاوا تمرکز کردیم، در حالی که در این قسمت قرار است از سویفت استفاده کنیم.
این فرآیند از بسیاری جهات مشابه خواهد بود، به جز روش پل زدن، که کمی متفاوت است.
در قسمت اول این مجموعه نحوه ایجاد ویجت اندروید با استفاده از جاوا را نشان خواهیم داد.
1. مقدمه
به منظور ساخت ویجت، ما یک پسوند ساده در xCode و همچنین یک “پل” ایجاد خواهیم کرد که به برنامه ما اجازه می دهد تا با ویجت ارتباط برقرار کند. شروع کنیم!
2. ایجاد ویجت
می توانیم با باز کردن پروژه خود در xCode شروع کنیم. اکنون روی پوشه پروژه کلیک راست کرده و به مسیر file > new > target بروید.
با این کار یک پنجره جدید با پسوندهای مختلف باز می شود. بیایید “افزونه ابزارک” را جستجو کنیم.
بعد، می توانیم نام محصول ویجت خود را انتخاب کنیم.
من قصد دارم نام آن را “BarcodeWidget” بگذارم. مطمئن شوید که علامت «شامل هدف پیکربندی» و «شامل فعالیت زنده» را بردارید.
اگر روی دکمه finish کلیک کنیم، xCode از ما میپرسد که آیا میخواهیم یک طرح فعال ایجاد کنیم. “فعال کردن” را انتخاب کنید.
اکنون میتوانیم پسوند ویجت خود را در قسمت “هدفها” انتخاب کرده و آن را اجرا کنیم. حتماً آن را برای iOS 14.0 و بالاتر اجرا کنید، زیرا نسخههای قبلی iOS با افزونه سازگار نیستند.
در این مرحله، ما باید بتوانیم ویجت خود را در صفحه اصلی ببینیم. در حال حاضر، فقط یک تاریخ ساده را نمایش می دهد.
بیایید نگاهی به فایل های ایجاد شده در پروژه خود بیاندازیم. می بینیم که xCode یک پوشه جدید با همان نامی که در ابتدا برای ویجت تعیین کرده بودیم ایجاد کرد.
برای اصلاح ویجت ما می توانیم فایل “BarcodeWidget.swift” را باز کنیم.
توابع و ساختارهای مختلفی در داخل فایل وجود دارد. بیایید نگاهی به چند مورد از آنها بیندازیم:
- “ساختار BarcodeWidget” با پیکربندی اصلی ویجت ما سروکار دارد. در اینجا، می توانیم نام نمایشی و همچنین توضیحات ویجت را تنظیم کنیم. اگر میخواهیم ویجت ما غیرقابل تغییر اندازه و اندازه خاص باشد (پیشنمایش پیشفرض سه گزینه کوچک، متوسط و بزرگ را ارائه میدهد)، میتوانیم آن را با فراخوانی تابع supportedFamilies() و ارسال یک آرگومان در اینجا تنظیم کنیم. آرایه ای که شامل اندازه های ویجت مورد نظر ما است.
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
- بعد، ساختار “BarcodeWidgetEntryView” وجود دارد که در آن میتوانیم طرحبندی ویجت را تغییر دهیم.
struct BarcodeWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
- همچنین ممکن است روش “getTimeline()” را پیدا کنید. این مجموعه ای از ورودی های جدول زمانی را برای زمان فعلی و به صورت اختیاری، هر زمان آینده برای به روز رسانی یک ویجت فراهم می کند.
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
اگر می خواهید درک بهتری از TimelineProvider و عملکردهای آن داشته باشید، همیشه می توانید اسناد رسمی اپل را در اینجا بررسی کنید.
3. پل زدن
برای نمایش دادهها از برنامه React Native، به تنظیمات اضافی نیاز داریم.
ابتدا، اجازه دهید قابلیت “App Group” را به پروژه خود اضافه کنیم تا ویجت با برنامه ارتباط برقرار کند.
هدف اصلی برنامه خود را در تنظیمات پروژه Xcode پیدا کنید، به برگه Signing & Capabilities بروید و روی + Capability کلیک کنید.
سپس عبارت “App Groups” را جستجو کرده و روی آن دوبار کلیک کنید.
روی دکمه + در قابلیت جدید “App Groups” ایجاد شده کلیک کنید و تیم خود را انتخاب کنید. مرحله آخر اضافه کردن شناسه گروه برنامه است. پس از اتمام، باید شناسه گروه برنامه خود را مشاهده کنید. اگر قرمز شد – حتما دکمه رفرش را امتحان کنید.
اکنون، ما می توانیم همین کار را برای هدف ویجت خود انجام دهیم. میتوانیم برگه امضا و قابلیتهای ویجت را باز کنیم و یک قابلیت «گروههای برنامه» جدید اضافه کنیم. اگر همان تیم قبلی را انتخاب کنیم، باید بتوانیم شناسهای را که قبلاً ایجاد کردهایم در هدف پروژه اصلی خود ببینیم. اطمینان حاصل کنید که شناسه یکسانی در هر دو هدف انتخاب شده است.
قبل از شروع نوشتن کد خود در React Native باید یک چیز دیگر را تنظیم کنیم. این شامل نصب بسته “SharedGroupPreferences” است. از ترمینال، اجرا کنید:
npm i react-native-shared-group-preferences
فایل App خود را باز کنید و وارد کنید:
import SharedGroupPreferences from 'react-native-shared-group-preferences'
حالا بیایید این را به کد خود اضافه کنیم:
// Let's display a random 6 digit number
const barcode = Math.floor(100000 + Math.random() * 900000).toString()
const appGroupIdentifier="group.widget.barcode.jp"
useEffect(() => {
if (Platform.OS === 'ios'){
const setWidgetData = async () => {await SharedGroupPreferences.setItem('widgetKey', {
text: barcode !== undefined ? barcode : '',
}, appGroupIdentifier)}
setWidgetData()
.catch((error) => {
log.info(() => ['error setting widget data, err: ', error])
},[barcode])
«appGroupIdentifier» باید همان چیزی باشد که در «گروههای برنامه» خود تنظیم کردیم.
عالی! کار ما با بخش React Native تمام شده است. حال، بیایید به کد ویجت خود برگردیم.
برای افزودن متن جدید به ویجت خود، ابتدا باید کد زیر را در بالای Provider خود اضافه کنیم:
struct WidgetData: Decodable {
var text: String
}
در مرحله بعد، ساختار “SimpleEntry” را به روز کنید:
struct SimpleEntry: TimelineEntry {
let date: Date
let myString: String
}
متغیر “myString” رشته بارکد ما خواهد بود که در برنامه React Native تنظیم می کنیم. وقتی این را اضافه می کنیم، خطایی ظاهر می شود – به شما می گوید تعداد آرگومان های روش های خاص را به روز کنید. پس از رفع این خطا، کد ما باید چیزی شبیه به این باشد:
حالا بیایید متد “getTimeline()” خود را با کد زیر به روز کنیم:
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let userDefaults = UserDefaults.init(suiteName: "group.widget.barcode.jp")
if userDefaults != nil {
if let savedData = userDefaults!.value(forKey: "widgetKey") as? String {
let decoder = JSONDecoder()
let data = savedData.data(using: .utf8)
if let parsedData = try? decoder.decode(WidgetData.self, from: data!) {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .second, value: 3, to: currentDate)!
let entry = SimpleEntry(date: entryDate, myString: parsedData.text)
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
} else {
print("Could not parse data")
}
} else {
let currentDate = Date()
for hourOffset in 0 ..< 2 {
let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, myString: "No data")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
}
مقدار “suiteName” را با شناسه گروه برنامه خود در خط زیر جایگزین کنید:
UserDefaults.init(suiteName: "group.widget.barcode.jp")
آخرین چیزی که باید قبل از اجرای مجدد ویجت تنظیم کنیم، ساختار “BarcodeWidgetEntryView” است.
برای نمایش متن بارکد از برنامه React Native – اجازه دهید این را اضافه کنیم:
`struct BarcodeWidgetEntryView : View {
ورود var: Provider.Entry
var body: some View {
Text(entry.myString).font(.system(size: 12)).tracking(2)
}
}`
دویدن را به خاطر بسپار pod install
در پوشه iOS شما
حالا بیایید برنامه را شروع کنیم.
اگر همه چیز به خوبی کار کند، شماره بارکدی که قبلاً در برنامه خود تنظیم کرده بودیم در ویجت نمایش داده می شود.
4. تولید تصویر بارکد از یک مقدار رشته
تنها کاری که باید انجام دهید شامل تولید یک تصویر بارکد از شماره بارکد ما و همچنین نمایش آن است.
برای انجام این کار، ما قصد داریم چند تابع را به فایل “BarcodeWidget.swift” خود اضافه کنیم.
اولین تابعی که می خواهیم اضافه کنیم “generateBarcode()” است:
func generateBarcode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CICode128BarcodeGenerator") {
filter.setDefaults()
//Margin
filter.setValue(1.00, forKey: "inputQuietSpace")
filter.setValue(data, forKey: "inputMessage")
//Scaling
let transform = CGAffineTransform(scaleX: 3, y: 3)
if let output = filter.outputImage?.transformed(by: transform) {
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(output, from: output.extent)!
let rawImage:UIImage = UIImage.init(cgImage: cgImage)
//Refinement code to allow conversion to NSData or share UIImage. Code here:
//http://stackoverflow.com/questions/2240395/uiimage-created-from-cgimageref-fails-with-uiimagepngrepresentation
let cgimage: CGImage = (rawImage.cgImage)!
let cropZone = CGRect(x: 0, y: 0, width: Int(rawImage.size.width), height: Int(rawImage.size.height))
let cWidth: size_t = size_t(cropZone.size.width)
let cHeight: size_t = size_t(cropZone.size.height)
let bitsPerComponent: size_t = cgimage.bitsPerComponent
//THE OPERATIONS ORDER COULD BE FLIPPED, ALTHOUGH, IT DOESN'T AFFECT THE RESULT
let bytesPerRow = (cgimage.bytesPerRow) / (cgimage.width * cWidth)
let context2: CGContext = CGContext(data: nil, width: cWidth, height: cHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: cgimage.bitmapInfo.rawValue)!
context2.draw(cgimage, in: cropZone)
let result: CGImage = context2.makeImage()!
let finalImage = UIImage(cgImage: result)
return finalImage
}
}
این تابع یک UIIimage از رشته ایجاد می کند.
در نهایت، اجازه دهید “BarcodeWidgetEntryView” را با افزودن موارد زیر به روز کنیم:
struct BarcodeWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
if let image = generateBarcode(from: entry.myString) {
Image(uiImage: image).resizable()
.imageScale(.small).frame( width: CGFloat(132), height: CGFloat(60)).padding(.bottom, 3)
}
Text(entry.myString).font(.system(size: 12)).tracking(2)
}
}
هنگامی که دوباره برنامه را اجرا می کنیم، ویجت باید متن و تصویر را مانند زیر نمایش دهد:
5. به روز رسانی محتوای ویجت
اکنون، بیایید روی به روز نگه داشتن ویجت خود تمرکز کنیم.
ابتدا بیایید خط مشی بهروزرسانی خود را به «هرگز» تغییر دهیم، زیرا به برنامه دستور میدهیم که چه زمانی محتوای آن را بهروزرسانی کند.
let timeline = Timeline(entries: entries, policy: .never)
بیایید یک تابع “reloadWidget()” در کد سوئیفت ایجاد کنیم که بعداً در برنامه React Native خود فراخوانی خواهیم کرد.
این را می توان با افزودن دو فایل به پروژه ما انجام داد.
“فایل جدید” را انتخاب کنید و در پنجره بعدی “فایل سویفت” را انتخاب کنید.
سپس، اجازه دهید نام فایل را “WidgetModule” بگذاریم و برنامه خود را به عنوان هدف این فایل انتخاب کنیم.
فایل “WidgetModule.swift” را مطابق شکل زیر به روز کنید:
import Foundation
import AVFoundation
import WidgetKit
@objc(WidgetModule)
class WidgetModule: NSObject {
@objc public func reloadWidget(_ kind: String) -> Void {
if #available(iOS 14.0, *) {
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.reloadAllTimelines()
#endif
}
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}
“reloadWidget” ما در حال استفاده است WidgetCenter.shared.reloadAllTimelines()
روش برای بارگذاری مجدد جدول زمانی ویجت. برای جزئیات بیشتر در مورد بارگیری مجدد ویجت، این پیوند را بررسی کنید.
حالا باید فایل دیگر را اضافه کنیم. “Objective-C” را انتخاب کنید:
بیایید آن را مانند فایل قبل از “WidgetModule” نامگذاری کنیم و همان هدف فایل را در فایل ایجاد شده قبلی انتخاب کنیم.
فایل را به صورت زیر آپدیت کنید:
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
@interface
RCT_EXTERN_MODULE(WidgetModule, NSObject)
RCT_EXTERN_METHOD(reloadWidget: (NSString *)kind)
@end
این فایل برای صادر کردن ماژول جدید ایجاد شده ما استفاده می شود که بعداً در برنامه React Native از آن استفاده خواهیم کرد.
آخرین فایلی که قرار است اضافه کنیم یک Bridge Header است. دوباره یک فایل جدید اضافه کنید و “Header” را انتخاب کنید.
کد زیر را قرار دهید:
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "React/RCTBridgeModule.h"
در نهایت اجازه دهید تابع “updateWidget” خود را از کد React Native فراخوانی کنیم.
ابتدا NativeModules را در فایل App وارد کنید:
“وارد کردن { NativeModules } از “react-native”
صادرات const برنامه = () => {
const { WidgetModule } = NativeModules
…
}
`
سپس، بیایید قلاب “useEffect” خود را با افزودن به روز کنیم.then(() => WidgetModule.reloadWidget(appGroupIdentifier))
به متد setWidgetData()
نتیجه نهایی باید به این صورت باشد:
if (Platform.OS === 'ios'){
const setWidgetData = async () => {await SharedGroupPreferences.setItem('widgetKey', {
text: barcode !== undefined ? barcode : '',
}, appGroupIdentifier)}
setWidgetData()
.then(() => WidgetModule.reloadWidget(appGroupIdentifier))
.catch((error) => {
log.info(() => ['error setting widget data, err: ', error])
})
7. پایان
خوب، به نظر می رسد ما تمام شده ایم!
دویدن را فراموش نکنید npm install && cd iOS && pod install
قبل از اجرای مجدد پروژه اکنون هر بار که تابع “updateWidget” را فراخوانی می کنیم، طرح ویجت باید به روز شود.
با خیال راحت با گزینه های مختلف ویجت بازی کنید و سعی کنید سبکی به آن اضافه کنید.
در پروژه خود موفق باشید، امیدوارم این آموزش به شما کمک کرده باشد!