HomeiOS DevelopmentI cannot hyperlink IOS module to React Native CLI

I cannot hyperlink IOS module to React Native CLI


I’m creating widget app in react native cli. It’s a countdown widget that has 5 backgrounds which the consumer can decide. And the consumer also can add customized picture to the background of the widget. When i attempted to make use of NativeModules.RNWidgetModule, it’s being null. How am i able to remedy this?

(I’m not IOS developer. I wrote ios codes with AI.)

SkinPicker.ts

/* eslint-disable react-native/no-inline-styles */
import React from 'react';
import {
  View,
  TouchableOpacity,
  StyleSheet,
  Textual content,
  BackHandler,
  Platform,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { NativeModules } from 'react-native';
const { WidgetModule } = NativeModules;

// Helper to use pores and skin
const setWidgetSkin = (skinName: string) => {
  if (Platform.OS === 'android' && WidgetModule) {
    WidgetModule.setSkin(skinName);
  }

  if (Platform.OS === 'ios' && NativeModules.RNWidgetModule) {
    NativeModules.RNWidgetModule.setSkin(skinName);
    NativeModules.RNWidgetModule.reloadWidgets();
  }

  if (Platform.OS === 'android') {
    BackHandler.exitApp();
  }
};

// Pores and skin definitions
const skins = [
  {
    name: 'FIDESZ',
    type: 'solid',
    color: '#FF6A13',
  },
  {
    name: 'TISZA',
    type: 'gradient',
    colors: ['#24B573', '#ED4551'],
  },
  {
    title: 'KUTYAPART',
    sort: 'dots', // two-color cut up + two purple dots
    colours: ['#FFFFFF', '#000000'],
    dotColor: '#DA0000',
  },
  {
    title: 'DK',
    sort: 'gradient',
    colours: ['#0062A7', '#C50067', '#FFD500', '#2DAAE1'],
  },
  {
    title: 'MI_HAZANK',
    sort: 'stable',
    shade: '#678B1D',
  },
];

export default operate SkinPicker() {
  return (
    
      Háttér kiválasztása
      
        {skins.map(pores and skin => {
          if (pores and skin.sort === 'stable') {
            return (
               setWidgetSkin(skin.name)}
              />
            );
          } else if (skin.type === 'gradient') {
            return (
               setWidgetSkin(skin.name)}
              >
                
              
            );
          } else if (skin.type === 'dots') {
            // Split box + 2 red dots
            return (
               setWidgetSkin(skin.name)}
              >
                
                  
                  
                  {/* Red dots */}
                  
                  
                
              
            );
          }
        })}
      
    
  );
}

const styles = StyleSheet.create({
  headerText: {
    textAlign: 'center',
    fontSize: 20,
    marginBottom: 10,
  },
  row: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    gap: 10,
  },
  box: {
    width: 50,
    height: 50,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#333',
    overflow: 'hidden',
  },
  dot: {
    position: 'absolute',
    width: 8,
    height: 8,
    borderRadius: 4,
  },
});

RNWidgetModule.m

#import 

@interface RCT_EXTERN_MODULE(RNWidgetModule, NSObject)

RCT_EXTERN_METHOD(setSkin:(NSString *)skinName)

RCT_EXTERN_METHOD(setCustomBackground:(NSString *)imageUri
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(removeCustomBackground)

RCT_EXTERN_METHOD(reloadWidgets)

@end

RNWidgetModule.swift

import Foundation
import React
import WidgetKit
import UIKit

@objc(RNWidgetModule)
class RNWidgetModule: NSObject {
    
    @objc
    static func requiresMainQueueSetup() -> Bool {
        return false
    }
    
    @objc
    func setSkin(_ skinName: String) {
        DispatchQueue.main.async {
            guard let userDefaults = UserDefaults(suiteName: "group.ittazido") else {
                print("Failed to get UserDefaults with suite name")
                return
            }
            
            userDefaults.set(skinName, forKey: "selectedSkin")
            userDefaults.set(false, forKey: "isCustomBackground")
            userDefaults.removeObject(forKey: "customBackgroundData")
            WidgetCenter.shared.reloadAllTimelines()
            userDefaults.synchronize()
            
            print("Skin set to: (skinName)")
            self.reloadWidgets()
        }
    }
    
    @objc
    func setCustomBackground(_ imageUri: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            guard let url = URL(string: imageUri) else {
                rejecter("INVALID_URI", "Invalid image URI", nil)
                return
            }
            
            // Handle different URI schemes
            var imageData: Data?
            
            if imageUri.hasPrefix("ph://") {
                // Photo library asset
                self.loadPhotoLibraryAsset(url: url) { data in
                    if let data = data {
                        self.saveCustomBackground(data: data, resolver: resolver, rejecter: rejecter)
                    } else {
                        rejecter("LOAD_FAILED", "Failed to load image from photo library", nil)
                    }
                }
                return
            } else if imageUri.hasPrefix("file://") {
                // File system
                let filePath = url.path
                imageData = NSData(contentsOfFile: filePath) as Data?
            } else if imageUri.hasPrefix("data:") {
                // Base64 data URI
                if let range = imageUri.range(of: ",") {
                    let base64String = String(imageUri[range.upperBound...])
                    imageData = Information(base64Encoded: base64String)
                }
            }
            
            guard let knowledge = imageData else {
                rejecter("LOAD_FAILED", "Did not load picture knowledge", nil)
                return
            }
            
            self.saveCustomBackground(knowledge: knowledge, resolver: resolver, rejecter: rejecter)
        }
    }
    
    personal func loadPhotoLibraryAsset(url: URL, completion: @escaping (Information?) -> Void) {
        import Pictures
        
        let assetId = url.absoluteString.replacingOccurrences(of: "ph://", with: "")
        let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], choices: nil)
        
        guard let asset = fetchResult.firstObject else {
            completion(nil)
            return
        }
        
        let imageManager = PHImageManager.default()
        let choices = PHImageRequestOptions()
        choices.isSynchronous = false
        choices.deliveryMode = .highQualityFormat
        
        imageManager.requestImage(for: asset, targetSize: CGSize(width: 800, peak: 400), contentMode: .aspectFill, choices: choices) { picture, _ in
            guard let picture = picture else {
                completion(nil)
                return
            }
            
            completion(picture.jpegData(compressionQuality: 0.8))
        }
    }
    
    personal func saveCustomBackground(knowledge: Information, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
        guard let userDefaults = UserDefaults(suiteName: "group.ittazido") else {
            rejecter("USERDEFAULTS_ERROR", "Did not get UserDefaults", nil)
            return
        }
        
        // Resize picture if wanted
        guard let picture = UIImage(knowledge: knowledge) else {
            rejecter("INVALID_IMAGE", "Invalid picture knowledge", nil)
            return
        }
        
        let resizedImage = self.resizeImage(picture: picture, maxWidth: 800, maxHeight: 400)
        guard let resizedData = resizedImage.jpegData(compressionQuality: 0.8) else {
            rejecter("RESIZE_FAILED", "Did not resize picture", nil)
            return
        }
        
        userDefaults.set(true, forKey: "isCustomBackground")
        userDefaults.set(resizedData, forKey: "customBackgroundData")
        userDefaults.synchronize()
        
        self.reloadWidgets()
        resolver("Customized background set efficiently")
    }
    
    @objc
    func removeCustomBackground() {
        DispatchQueue.important.async {
            guard let userDefaults = UserDefaults(suiteName: "group.ittazido") else {
                return
            }
            
            userDefaults.set(false, forKey: "isCustomBackground")
            userDefaults.removeObject(forKey: "customBackgroundData")
            userDefaults.synchronize()
            
            self.reloadWidgets()
        }
    }
    
    @objc
    func reloadWidgets() {
        DispatchQueue.important.async {
            if #obtainable(iOS 14.0, *) {
                WidgetCenter.shared.reloadAllTimelines()
                print("Widgets reloaded")
            }
        }
    }
    
    personal func resizeImage(picture: UIImage, maxWidth: CGFloat, maxHeight: CGFloat) -> UIImage {
        let measurement = picture.measurement
        
        if measurement.width  String! {
        return "RNWidgetModule"
    }
}

CountdownWidget.swift

import WidgetKit
import SwiftUI
import Basis

struct CountdownData {
    let targetDate: Date
    let lastFetchTime: Date
}

enum WidgetSkin: String, CaseIterable {
    case fidesz = "FIDESZ"
    case tisza = "TISZA"
    case kutyapart = "KUTYAPART"
    case dk = "DK"
    case miHazank = "MI_HAZANK"
}

struct CountdownTimelineProvider: TimelineProvider {
    typealias Entry = CountdownEntry
    
    func placeholder(in context: Context) -> CountdownEntry {
        CountdownEntry(date: Date(), targetDate: getDefaultTargetDate())
    }
    
    func getSnapshot(in context: Context, completion: @escaping (CountdownEntry) -> Void) {
        let entry = CountdownEntry(date: Date(), targetDate: getTargetDate())
        completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
        fetchTargetDateFromAPI { targetDate in
            var entries: [CountdownEntry] = []
            let currentDate = Date()
            
            // Create entries for each second for the subsequent minute
            for secondOffset in 0.. Void) {
        guard let url = URL(string: "https://api.lefiko.hu/time") else {
            completion(getDefaultTargetDate())
            return
        }
        
        URLSession.shared.dataTask(with: url) { knowledge, response, error in
            guard let knowledge = knowledge,
                  let json = attempt? JSONSerialization.jsonObject(with: knowledge, choices: []) as? [String: Any],
                  let timeString = json["time"] as? String else {
                completion(getDefaultTargetDate())
                return
            }
            
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd H:mm:ss"
            formatter.timeZone = TimeZone(identifier: "Europe/Budapest")
            
            if let date = formatter.date(from: timeString) {
                // Cache the end result
                UserDefaults(suiteName: "group.ittazido")?.set(date, forKey: "targetDate")
                UserDefaults(suiteName: "group.ittazido")?.set(Date(), forKey: "lastFetchTime")
                completion(date)
            } else {
                completion(getDefaultTargetDate())
            }
        }.resume()
    }
    
    personal func getTargetDate() -> Date {
        guard let userDefaults = UserDefaults(suiteName: "group.ittazido"),
              let cachedDate = userDefaults.object(forKey: "targetDate") as? Date,
              let lastFetch = userDefaults.object(forKey: "lastFetchTime") as? Date,
              Date().timeIntervalSince(lastFetch)  Date {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd H:mm:ss"
        formatter.timeZone = TimeZone(identifier: "Europe/Budapest")
        return formatter.date(from: "2026-04-12 06:00:00") ?? Date()
    }
}

struct CountdownEntry: TimelineEntry {
    let date: Date
    let targetDate: Date
}

struct CountdownWidgetEntryView: View {
    var entry: CountdownEntry

    @State personal var now = Date()
    
    @AppStorage("selectedSkin", retailer: UserDefaults(suiteName: "group.ittazido"))
    personal var selectedSkin: String = "DK"
    
    @AppStorage("isCustomBackground", retailer: UserDefaults(suiteName: "group.ittazido"))
    personal var isCustomBackground: Bool = false
    
    personal var currentSkin: WidgetSkin {
        WidgetSkin(rawValue: selectedSkin) ?? .dk
    }
    
    personal var customBackgroundData: Information? {
        UserDefaults(suiteName: "group.ittazido")?.knowledge(forKey: "customBackgroundData")
    }
    
    var physique: some View {
        ZStack {
            // Background
            if isCustomBackground, let imageData = customBackgroundData, let uiImage = UIImage(knowledge: imageData) {
                Picture(uiImage: uiImage)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else {
                getSkinBackground(for: currentSkin)
            }
            
            // Overlay purple dots for KUTYAPART
            if currentSkin == .kutyapart {
                VStack {
                    HStack {
                        Circle()
                            .fill(Colour.purple)
                            .body(width: 8, peak: 8)
                        Spacer()
                    }
                    Spacer()
                    HStack {
                        Spacer()
                        Circle()
                            .fill(Colour.purple)
                            .body(width: 8, peak: 8)
                    }
                }
                .padding(5)
            }
            
            // Countdown textual content
            Textual content(getCountdownText())
                .font(.system(measurement: 24, weight: .daring))
                .foregroundColor(getTextColor(for: currentSkin))
                .shadow(shade: .black.opacity(0.5), radius: 2, x: 1, y: 1)
                .multilineTextAlignment(.heart)
    
        }
        .containerBackground(for: .widget) {
            Colour.clear
        }
    }
    
    personal func getCountdownText() -> String {
        let now = entry.date
        let goal = entry.targetDate
        let diff = goal.timeIntervalSince(now)
        
        if diff > 0 {
            let days = Int(diff) / (24 * 60 * 60)
            let hours = (Int(diff) % (24 * 60 * 60)) / (60 * 60)
            let minutes = (Int(diff) % (60 * 60)) / 60
            let seconds = Int(diff) % 60
            return String(format: "%d nap %02d:%02d:%02d", days, hours, minutes, seconds)
        } else if diff >= -24 * 60 * 60 {
            return "ITT AZ IDŐ!"
        } else {
            return "4 év múlva újra találkozunk!"
        }
    }
    
    personal func getSkinBackground(for pores and skin: WidgetSkin) -> some View {
        Group {
            swap pores and skin {
            case .fidesz:
                RoundedRectangle(cornerRadius: 8)
                    .fill(Colour(purple: 1.0, inexperienced: 0.416, blue: 0.075))
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Colour.black.opacity(0.2), lineWidth: 1)
                    )
                
            case .tisza:
                RoundedRectangle(cornerRadius: 8)
                    .fill(
                        LinearGradient(
                            colours: [
                                Color(red: 0.141, green: 0.710, blue: 0.451),
                                Color(red: 0.929, green: 0.271, blue: 0.318)
                            ],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Colour.black.opacity(0.2), lineWidth: 1)
                    )
                
            case .kutyapart:
                RoundedRectangle(cornerRadius: 8)
                    .fill(
                        LinearGradient(
                            colours: [Color.white, Color.black],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Colour.black.opacity(0.2), lineWidth: 1)
                    )
                
            case .dk:
                RoundedRectangle(cornerRadius: 8)
                    .fill(
                        LinearGradient(
                            colours: [
                                Color(red: 0.004, green: 0.384, blue: 0.655),
                                Color(red: 0.773, green: 0.000, blue: 0.404),
                                Color(red: 1.0, green: 0.835, blue: 0.0)
                            ],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Colour.black.opacity(0.2), lineWidth: 1)
                    )
                
            case .miHazank:
                RoundedRectangle(cornerRadius: 8)
                    .fill(Colour(purple: 0.404, inexperienced: 0.545, blue: 0.114))
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Colour.black.opacity(0.2), lineWidth: 1)
                    )
            }
        }
    }
    
    personal func getTextColor(for pores and skin: WidgetSkin) -> Colour {
        swap pores and skin {
        case .kutyapart:
            return .black
        default:
            return .white
        }
    }
}

struct CountdownWidget: Widget {
    let variety: String = "CountdownWidget"
    
    var physique: some WidgetConfiguration {
        StaticConfiguration(variety: variety, supplier: CountdownTimelineProvider()) { entry in
            CountdownWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Countdown Widget")
        .description("Shows a countdown to the goal date.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

#Preview(as: .systemSmall) {
    CountdownWidget()
} timeline: {
    let targetDate = Calendar.present.date(byAdding: .day, worth: 30, to: Date()) ?? Date()
    CountdownEntry(date: .now, targetDate: targetDate)
}

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments