2023年7月30日 星期日

iOS測試IPv6環境

 簡述:分享Mac的WiFi給手機。打開分享時,需按住Option,分享才會有NAT64。

詳細方法,參考這篇的Test for IPv6 DNS64/NAT64 Compatibility Regularly。

值得注意,用USB線分享的話,會是IPv4+IPv6

2022年6月15日 星期三

Swift Decodable 找不到key時,使用預設值

使用JSONDecoder時,如果找不到Key值,錯誤如下

▿ DecodingError
  ▿ keyNotFound : 2 elements
    - .0 : CodingKeys(stringValue: "b", intValue: nil)
    ▿ .1 : Context
      - codingPath : 0 elements
      - debugDescription : "No value associated with key CodingKeys(stringValue: \"b\", intValue: nil) (\"b\")."
      - underlyingError : nil

其實只要利用KeyedDecodingContainer的decode(type:forKey)就可以解決這問題

protocol Init {
    init()
}

extension KeyedDecodingContainer {
    func decode<T: Codable & Init>(_ type: T.Type,
                forKey key: Key) throws -> T {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

之後只要將Codable後面,加上Init這個protocal就可以了

enum B: Int, Codable {
    case one = 1
    case two = 2
}

extension B: Init {     init() {         self = .one     } }

2022年3月17日 星期四

用swift讀寫NTag


  1. 在info.plist中,加入Privacy - NFC Scan Usage Description
  2. 在Capabilities 加入Near Field Communication Tag Reading
  3. 掃描

    func startScan() {

            let session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)

            session.alertMessage = "Hold your iPhone near an NFC transit card."

            session.begin()

    }

  4. 連線,更新payload

    func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {

        guard

            let sms = "場所代碼:111111111111111 本次實聯簡訊限防疫目的使用。".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),

            let payload = NFCNDEFPayload.wellKnownTypeURIPayload(string: "sms:1922&body=\(sms)"),

            let tag = tags.first

        else {

            session.invalidate(errorMessage: "Could not process tag.")

            return

        }

        session.connect(to: tag) { error in

            guard error == nil else {

                session.invalidate(errorMessage: "Could not connect to tag.")

                return

            }

            tag.queryNDEFStatus { status, capacity, error in

                guard error == nil else {

                    session.invalidate(errorMessage: "Could not query status of tag.")

                    return

                }

                

                switch status {

                case .notSupported:

                    session.invalidate(errorMessage: "Tag is not supported.")

                case .readOnly:

                    session.invalidate(errorMessage: "Tag is only readable.")

                case .readWrite:

                    let messge = NFCNDEFMessage.init(records: [payload])

                    tag.writeNDEF(messge) { error in

                        if error != nil {

                            session.invalidate(errorMessage: "Failed to write message.")

                        } else {

                            session.alertMessage = "Successfully configured tag."

                            session.invalidate()

                        }

                    }

                    

                @unknown default:   session.invalidate(errorMessage: "Unknown status of tag.")

                }

            }

        }

    }


PS.找不到 Near Field Communication Tag Reading ,請切換開發者帳號到付費帳號

2022年3月5日 星期六

ARKit(SpriteKit) 臉部角度追蹤


  1. 建立😘 。由於前鏡頭左右相反,所以X軸要反向。

    let labelNode = SKLabelNode(text: "😘")

    labelNode.horizontalAlignmentMode = .center

    labelNode.verticalAlignmentMode = .center

    labelNode.xScale = -1

  2. 須將😘放入SKTransformNode,做後續的3D轉換。

    let face = SKTransformNode()

    face.addChild(labelNode)

    self.face = face

  3. 更新時,SKTransformNode再依據ARFaceAnchor旋轉

    let rotate = faceAnchor.transform.rotate

    face?.setRotationMatrix(rotate)

  4. 最後,記得將node轉回來,否則貼圖會是反的

完整程式

2022年2月26日 星期六

ARKit(SceneKit) 臉部貼圖

  1. 修改Tracking and Visualizing Faces範例中的貼圖『wireframeTexture.png』
  2. 指定ARSCNFaceGeometry的貼圖

    faceMesh?.firstMaterial?.diffuse.contents = UIImage(named: "wireframeTexture")

ARKit(SceneKit) 臉部表情追蹤

  1.  建立臉部追蹤
  2. // Create a session configuration

     let configuration = ARFaceTrackingConfiguration()

    // Run the view's session

    sceneView.session.run(configuration)

  3.  建立臉部模型
  4. func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

        if anchor is ARFaceAnchor {

            let faceMesh = ARSCNFaceGeometry(device: sceneView.device!)

            faceMesh?.firstMaterial?.lightingModel = .physicallyBased

            let node = SCNNode(geometry: faceMesh)

            return node

        } else {

            return nil

        }

    }

  5.  更新臉部表情
  6. func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

        if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {

            faceGeometry.update(from: faceAnchor.geometry)

        }

    }

2022年2月11日 星期五

RealityKit 判斷手指觸摸物件

  1. 用Reality Composer編輯物件『屬性』,勾選『物理效果』『加入』
  2. 將ARFrame.capturedImage轉成CVPixelBuffer

    func session(_ session: ARSession, didUpdate frame: ARFrame) {

            guard let arView = arView else {return}

            // Do not enqueue other buffers for processing while another Vision task is still running.

            // The camera stream has only a finite amount of buffers available; holding too many buffers for analysis would starve the camera.

            guard currentBuffer == nil, case .normal = frame.camera.trackingState else {

                return

            }

            // Retain the image buffer for Vision processing.

            let currentBuffer = frame.capturedImage

    }

  3. 利用Vision偵測食指

    let requestHandler = VNImageRequestHandler(cvPixelBuffer: currentBuffer, orientation: orientation)

    visionQueue.async {

            do {

                    // Perform VNDetectHumanHandPoseRequest

                    try requestHandler.perform([self.handPoseRequest])

                    // Continue only when a hand was detected in the frame.

                    // Since we set the maximumHandCount property of the request to 1, there will be at most one observation.

                    guard let observation = self.handPoseRequest.results?.first else {

                        return

                    }

                    // Get points for index finger.

                    let indexFingerPoints = try observation.recognizedPoints(.indexFinger)

                    // Look for tip points.

                    guard let indexTipPoint = indexFingerPoints[.indexTip] else {

                        return

                    }

                    // Ignore low confidence points.

                    guard indexTipPoint.confidence > 0.3 else {

                        return

                    }

                    

                    

            } catch {

                    print("Error: Vision request failed with error \"\(error)\"")

            }

    }

  4. 將Vision的座標轉成ARView的座標

    // Convert points from Vision coordinates to ARView coordinates.

    let point = indexTipPoint.location

    let tip = CGPoint(x: point.x * width, y: (1 - point.y) * height)


sample code

2022年2月10日 星期四

RealityKit 判斷物件點擊方法2

  1. 用Reality Composer編輯物件『屬性』,勾選『物理效果』『加入』
  2. 替arView加上手勢功能

    arView.addGestureRecognizer(

          UITapGestureRecognizer(

                target: xxxx,

                action: #selector(XXXX.handleTap(recognizer:))

        )

    )


  3. @objc func handleTap(recognizer: UITapGestureRecognizer) {

            let tapLocation = recognizer.location(in: arView)

            let entity = arView.entity(at: tapLocation)

            guard entity == boxAnchor?.steelBox else { return }

            // entity 即點擊物件

    }

RealityKit 判斷物件點擊方法1

 直接用Reality Composer編輯物件行為

  1. 選取物件
  2. 顯示行為(⌥⌘B)
  3. 加入行為『點按與反轉』

2021年8月25日 星期三

2019年5月2日 星期四

在Heroku上發佈MochiWeb

注意:
  1. 官方https://github.com/heroku/heroku-buildpack-erlang.git過舊(OTP和cebar),所以需要替代方案
  2. Heroku的port是亂數產生,所以需要動態拿到port
參考

2018年7月20日 星期五

2018年6月7日 星期四

iOS hot reloading

1. 安裝 InjectionIII 並啟動
2. 打開 XCode 和 專案
3. 在appDelegate加入

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.z
        #if DEBUG
        #if targetEnvironment(simulator)
        #if os(iOS)
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
        #elseif os(tvOS)
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()
        #endif
        #elseif os(macOS)
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
        #endif
        #endif
        return true
    }


PS.
已經秀出來的view並未改變。但是,新present出的view確實已經改變

如需要改變已秀出來的view,需在每個controller上實作
@objc func injected() {
    // 可馬上更新的code

}

建議的懶人法

    @objc func injected() {
        // 移除全部vc/view
        if self.childViewControllers.count > 0{
            let viewControllers:[UIViewController] = self.childViewControllers
            for viewContoller in viewControllers{
                viewContoller.willMove(toParentViewController: nil)
                viewContoller.view.removeFromSuperview()
                viewContoller.removeFromParentViewController()
            }
        }
        
        configureView()
    }
    
    func configureView() {
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()
    }

2018年3月18日 星期日

Redux特調

最近花了些時間,寫了一個redux的簡易範例,希望可以幫大家快速入門

原始碼

2017年12月14日 星期四

一直搞不懂bindActionCreators的應用原理,
花了一些時間研究,



參考資料

2017年10月11日 星期三

iOS簡繁互轉

UIAlertController客製化

根據🍎的文件
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.

如果不想理他的話,就用以下的方法吧!

  1. 打開contentViewController的束縛
    @interface UIAlertController (ContentViewController)
    @property (nonatomic,retain) UIViewController * contentViewController;
    @end
  2. 呼喚神秘的contentViewController
    alertController.contentViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "contentViewController")
  3. 如果,使用tableview的話,記得加上
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
            
        // make UITableView fit to content's size
        self.preferredContentSize = self.tableView.contentSize

    }

code

2017年1月10日 星期二

iOS launch image的黑魔法

  1. 開一個新專案,在iPhone 7 size是 414x736

  2. 移除LaunchScreen.storyboard,在iPhone 7 size是 320x480

  3. 新增Default-568h@2x.png和Default-568h@3x.png到專案中(就算是 1x1的圖也沒關係,重點是檔名要正確),在iPhone 7 size是 320x58