iOS 和 HarmonyOS 兼容笔记

1. iOS 审核域名限制问题

问题描述

iOS 审核人员位于国外,App 内访问的 API 域名需要能在外网访问,否则可能导致审核失败。

解决方案

确保所有 API 域名都能在外网访问,或提供审核专用的 API 环境。

代码示例

javascript 复制代码
// 配置 API 域名时考虑审核需求
const isProduction = process.env.NODE_ENV === 'production';
const isReview = process.env.VUE_APP_REVIEW === 'true';

// 审核环境使用可外网访问的域名
const baseUrl = isReview ? 'https://review-api.example.com' : 
               isProduction ? 'https://api.example.com' : 
               'http://dev-api.example.com';

export default {
  baseUrl
};

2. iOS scroll-view 内 fixed 定位弹框被遮挡

问题描述

在 iOS 设备上,scroll-view 组件内部的 fixed 定位弹框内容会被遮挡,无法正常显示。

解决方案

将弹框移出 scroll-view 组件,或使用其他定位方式。

代码示例

html 复制代码
<!-- 错误示例:fixed 定位弹框在 scroll-view 内部 -->
<scroll-view scroll-y="true" style="height: 100vh;">
  <view>scroll-view 内容</view>
  <view class="popup" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
    弹框内容
  </view>
</scroll-view>

<!-- 正确示例:fixed 定位弹框在 scroll-view 外部 -->
<scroll-view scroll-y="true" style="height: 100vh;">
  <view>scroll-view 内容</view>
</scroll-view>
<view class="popup" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
  弹框内容
</view>

3. iOS plus.io.chooseFile() 返回路径处理问题

问题描述

在 iOS 上,plus.io.chooseFile().then(res => {}) 返回的 res.files[0] 已经是可直接读取的绝对路径,不能再用 plus.io.convertLocalFileSystemURL 去转换,否则会拼接出不合法的路径,导致 uni.uploadFile 读取不到文件体。

解决方案

根据平台判断是否需要转换路径。

代码示例

javascript 复制代码
plus.io.chooseFile({
  title: '选择文件',
  filter: {
    mimeTypes: ['image/*']
  }
}).then(res => {
  let filePath = res.files[0];
  
  // 根据平台判断是否需要转换路径
  if (uni.getSystemInfoSync().platform !== 'ios') {
    // 非 iOS 平台需要转换路径
    filePath = plus.io.convertLocalFileSystemURL(filePath);
  }
  
  // 使用转换后的路径进行文件上传
  uni.uploadFile({
    url: 'https://api.example.com/upload',
    filePath: filePath,
    name: 'file',
    success: (uploadRes) => {
      console.log('上传成功', uploadRes);
    },
    fail: (err) => {
      console.error('上传失败', err);
    }
  });
});

4. HarmonyOS picker 组件异步数据渲染问题

问题描述

在 HarmonyOS 设备上,内置组件 picker 的 range 使用异步数据时,需要使用 v-if 或 v-show 控制在获取到数据后再渲染 picker 组件,否则 picker 组件弹框不会显示。

解决方案

使用 v-if 或 v-show 控制 picker 组件的渲染时机,确保在数据加载完成后再渲染。

代码示例

vue 复制代码
<template>
  <view>
    <button @click="showPicker = true">显示选择器</button>
    
    <!-- 使用 v-if 控制 picker 组件渲染时机 -->
    <picker
      v-if="pickerData.length > 0"
      v-model="selectedIndex"
      :range="pickerData"
      @change="onPickerChange"
      v-show="showPicker"
    ></picker>
  </view>
</template>

<script>
export default {
  data() {
    return {
      pickerData: [],
      selectedIndex: 0,
      showPicker: false
    };
  },
  onLoad() {
    // 异步获取数据
    this.loadPickerData();
  },
  methods: {
    loadPickerData() {
      // 模拟异步请求
      setTimeout(() => {
        this.pickerData = ['选项1', '选项2', '选项3', '选项4', '选项5'];
      }, 1000);
    },
    onPickerChange(e) {
      console.log('选择了', this.pickerData[e.detail.value]);
      this.showPicker = false;
    }
  }
};
</script>

5. HarmonyOS uni.getLocation 坐标系转换问题

问题描述

在鸿蒙设备上使用 uni.getLocation 获取定位是通过鸿蒙系统定位,获取到的定位坐标系是国际坐标系(wgs84),需要进行坐标系转换得到国测局坐标系(gcj02)才能在高德地图 API 上使用。

解决方案

使用坐标系转换算法将 wgs84 转换为 gcj02。

代码示例

javascript 复制代码
// 坐标系转换算法(wgs84 转 gcj02)
function wgs84togcj02(lng, lat) {
  const pi = 3.1415926535897932384626;
  const a = 6378245.0;
  const ee = 0.00669342162296594323;
  
  if (out_of_china(lng, lat)) {
    return [lng, lat];
  } else {
    let dlat = transformlat(lng - 105.0, lat - 35.0);
    let dlng = transformlng(lng - 105.0, lat - 35.0);
    const radlat = lat / 180.0 * pi;
    let magic = Math.sin(radlat);
    magic = 1 - ee * magic * magic;
    const sqrtmagic = Math.sqrt(magic);
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi);
    dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * pi);
    const mglat = lat + dlat;
    const mglng = lng + dlng;
    return [mglng, mglat];
  }
}

function transformlat(lng, lat) {
  const pi = 3.1415926535897932384626;
  let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
  ret += (20.0 * Math.sin(6.0 * lng * pi) + 20.0 * Math.sin(2.0 * lng * pi)) * 2.0 / 3.0;
  ret += (20.0 * Math.sin(lat * pi) + 40.0 * Math.sin(lat / 3.0 * pi)) * 2.0 / 3.0;
  ret += (160.0 * Math.sin(lat / 12.0 * pi) + 320 * Math.sin(lat * pi / 30.0)) * 2.0 / 3.0;
  return ret;
}

function transformlng(lng, lat) {
  const pi = 3.1415926535897932384626;
  let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
  ret += (20.0 * Math.sin(6.0 * lng * pi) + 20.0 * Math.sin(2.0 * lng * pi)) * 2.0 / 3.0;
  ret += (20.0 * Math.sin(lng * pi) + 40.0 * Math.sin(lng / 3.0 * pi)) * 2.0 / 3.0;
  ret += (150.0 * Math.sin(lng / 12.0 * pi) + 300.0 * Math.sin(lng / 30.0 * pi)) * 2.0 / 3.0;
  return ret;
}

function out_of_china(lng, lat) {
  return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);
}

// 使用示例
uni.getLocation({
  type: 'gcj02',
  success: (res) => {
    let lng = res.longitude;
    let lat = res.latitude;
    
    // 如果是鸿蒙设备,需要进行坐标系转换
    const systemInfo = uni.getSystemInfoSync();
    if (systemInfo.platform === 'harmony') {
      // 鸿蒙设备获取的是 wgs84 坐标系,需要转换为 gcj02
      const gcj02 = wgs84togcj02(lng, lat);
      lng = gcj02[0];
      lat = gcj02[1];
    }
    
    console.log('转换后的坐标', lng, lat);
    // 使用转换后的坐标调用高德地图 API
  }
});

6. HarmonyOS uni.chooseImage 不兼容 5.0

问题描述

uni.chooseImage 不兼容 HarmonyOS 5.0,在该版本上无法正常使用。

解决方案

使用 plus.io.chooseFile 替代 uni.chooseImage,或使用其他兼容方案。

代码示例

javascript 复制代码
// 兼容 HarmonyOS 5.0 的图片选择方法
function chooseImage(options) {
  const systemInfo = uni.getSystemInfoSync();
  
  // 如果是 HarmonyOS 5.0 或以上版本,使用 plus.io.chooseFile
  if (systemInfo.platform === 'harmony' && parseFloat(systemInfo.osVersion) >= 5.0) {
    return new Promise((resolve, reject) => {
      plus.io.chooseFile({
        title: '选择图片',
        filter: {
          mimeTypes: ['image/*']
        },
        multiple: options.count > 1,
        maxSelect: options.count
      }).then(res => {
        const tempFilePaths = res.files.map(file => {
          return plus.io.convertLocalFileSystemURL(file);
        });
        resolve({ tempFilePaths });
      }).catch(err => {
        reject(err);
      });
    });
  } else {
    // 其他平台使用 uni.chooseImage
    return new Promise((resolve, reject) => {
      uni.chooseImage({
        count: options.count,
        success: resolve,
        fail: reject
      });
    });
  }
}

// 使用示例
chooseImage({ count: 1 }).then(res => {
  console.log('选择的图片', res.tempFilePaths);
}).catch(err => {
  console.error('选择图片失败', err);
});

7. 高频同步 I/O 操作导致卡顿/闪退

问题描述

在列表渲染期间,高频地在主线程上执行同步 I/O 操作,会严重阻塞 UI 渲染,导致应用卡顿、无响应(ANR),而在一些对主线程管控更严格的系统(如 HarmonyOS Next)上,这很容易被判定为异常并导致闪退。

解决方案

将同步 I/O 操作改为异步,或减少调用次数,或使用缓存。

代码示例

vue 复制代码
<template>
  <view>
    <list>
      <list-item v-for="item in listData" :key="item.id">
        <text>{{ item.title }}</text>
        <text>{{ item.status }}</text>
      </list-item>
    </list>
  </view>
</template>

<script>
export default {
  data() {
    return {
      listData: []
    };
  },
  onLoad() {
    this.loadListData();
  },
  methods: {
    // 错误示例:列表渲染时多次调用 uni.getStorageSync
    loadListData() {
      // 模拟获取列表数据
      const list = [];
      for (let i = 0; i < 100; i++) {
        list.push({ id: i, title: '项目' + i });
      }
      
      // 错误:在循环中多次调用同步 I/O 操作
      list.forEach(item => {
        // 这会导致严重的性能问题
        const status = uni.getStorageSync(`item_status_${item.id}`);
        item.status = status || '默认状态';
      });
      
      this.listData = list;
    }
  }
};
</script>

<script>
// 正确示例:使用异步方法或减少调用次数
export default {
  data() {
    return {
      listData: []
    };
  },
  onLoad() {
    this.loadListData();
  },
  methods: {
    async loadListData() {
      // 模拟获取列表数据
      const list = [];
      for (let i = 0; i < 100; i++) {
        list.push({ id: i, title: '项目' + i });
      }
      
      // 正确:一次性获取所有需要的数据
      const allStatus = await this.getAllItemStatus(list);
      
      // 合并数据
      list.forEach(item => {
        item.status = allStatus[`item_status_${item.id}`] || '默认状态';
      });
      
      this.listData = list;
    },
    // 一次性获取所有需要的数据
    getAllItemStatus(list) {
      return new Promise((resolve) => {
        // 在实际应用中,可以使用异步方法批量获取数据
        // 这里模拟一次性获取所有状态
        const result = {};
        // 模拟异步操作
        setTimeout(() => {
          list.forEach(item => {
            result[`item_status_${item.id}`] = '状态' + item.id;
          });
          resolve(result);
        }, 100);
      });
    }
  }
};
</script>

8. HarmonyOS 页面多次调用 uni.getStorageSync 性能问题

问题描述

页面中多次调用 uni.getStorageSync 类似的同步方法,在鸿蒙设备上非常消耗性能,会造成页面加载渲染卡顿。

解决方案

将多次调用改为单次调用,或使用异步方法,或使用缓存。

代码示例

javascript 复制代码
// 错误示例:多次调用 uni.getStorageSync
function loadUserData() {
  const userId = uni.getStorageSync('user_id');
  const userName = uni.getStorageSync('user_name');
  const userAvatar = uni.getStorageSync('user_avatar');
  const userGender = uni.getStorageSync('user_gender');
  const userAge = uni.getStorageSync('user_age');
  
  return {
    userId,
    userName,
    userAvatar,
    userGender,
    userAge
  };
}

// 正确示例:使用异步方法一次性获取所有数据
async function loadUserData() {
  // 方式一:使用异步方法
  const userInfo = await uni.getStorage({ key: 'user_info' });
  
  // 方式二:如果数据分散存储,使用 Promise.all 并行获取
  /*
  const [userIdRes, userNameRes, userAvatarRes, userGenderRes, userAgeRes] = await Promise.all([
    uni.getStorage({ key: 'user_id' }),
    uni.getStorage({ key: 'user_name' }),
    uni.getStorage({ key: 'user_avatar' }),
    uni.getStorage({ key: 'user_gender' }),
    uni.getStorage({ key: 'user_age' })
  ]);
  
  const userInfo = {
    userId: userIdRes.data,
    userName: userNameRes.data,
    userAvatar: userAvatarRes.data,
    userGender: userGenderRes.data,
    userAge: userAgeRes.data
  };
  */
  
  return userInfo.data;
}

// 正确示例:将分散的数据合并存储
// 存储数据时
uni.setStorageSync('user_info', {
  userId: '123',
  userName: '张三',
  userAvatar: 'https://example.com/avatar.jpg',
  userGender: '男',
  userAge: 25
});

9. uni. <math xmlns="http://www.w3.org/1998/Math/MathML"> o n 、 u n i . on、uni. </math>on、uni.emit 跨页面通信在 H5 打包后失效

问题描述

uni.$onuni.$emit 事件,在打包成 H5 页面后不能跨页面通信。

解决方案

使用其他跨页面通信方式,如 URL 参数、localStorage、vuex 等。

代码示例

javascript 复制代码
// 方案一:使用 URL 参数传递数据
// 页面 A
uni.navigateTo({
  url: `/pages/pageB/pageB?data=hello`
});

// 页面 B
onLoad(options) {
  console.log(options.data); // hello
}

// 方案二:使用 localStorage 进行跨页面通信
// 页面 A
uni.setStorageSync('pageData', { message: 'hello' });

// 页面 B
onShow() {
  const data = uni.getStorageSync('pageData');
  console.log(data.message); // hello
}

// 方案三:使用 vuex 进行状态管理
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    message: ''
  },
  mutations: {
    setMessage(state, message) {
      state.message = message;
    }
  },
  actions: {
    updateMessage({ commit }, message) {
      commit('setMessage', message);
    }
  },
  getters: {
    getMessage(state) {
      return state.message;
    }
  }
});

// 页面 A
import store from '@/store';
store.dispatch('updateMessage', 'hello');

// 页面 B
import store from '@/store';
onShow() {
  console.log(store.getters.getMessage); // hello
}

// 方案四:使用事件总线(适用于 Vue 2)
// main.js
Vue.prototype.$bus = new Vue();

// 页面 A
this.$bus.$emit('customEvent', { message: 'hello' });

// 页面 B
created() {
  this.$bus.$on('customEvent', (data) => {
    console.log(data.message); // hello
  });
}

总结

以上是 iOS 和 HarmonyOS 兼容开发中的常见问题及解决方案,包括:

  1. iOS 审核域名限制问题
  2. iOS scroll-view 内 fixed 定位弹框被遮挡
  3. iOS plus.io.chooseFile() 返回路径处理问题
  4. HarmonyOS picker 组件异步数据渲染问题
  5. HarmonyOS uni.getLocation 坐标系转换问题
  6. HarmonyOS uni.chooseImage 不兼容 5.0
  7. 高频同步 I/O 操作导致卡顿/闪退
  8. HarmonyOS 页面多次调用 uni.getStorageSync 性能问题
  9. uni. <math xmlns="http://www.w3.org/1998/Math/MathML"> o n 、 u n i . on、uni. </math>on、uni.emit 跨页面通信在 H5 打包后失效

在开发跨平台应用时,需要充分考虑不同平台的特性和限制,编写兼容代码,确保应用在各平台上都能正常运行。

相关推荐
apollo_qwe5 小时前
UniApp 请求封装实战:优雅实现 Token 无感刷新(附完整代码)
uni-app
2501_915918415 小时前
使用 HBuilder 上架 iOS 应用时常见的问题与应对方式
android·ios·小程序·https·uni-app·iphone·webview
2501_916007477 小时前
iOS 崩溃日志的分析方法,将崩溃日志与运行过程结合分析
android·ios·小程序·https·uni-app·iphone·webview
2501_916007478 小时前
React Native 混淆在真项目中的方式,当 JS 和原生同时暴露
javascript·react native·react.js·ios·小程序·uni-app·iphone
00后程序员张8 小时前
苹果应用商店上架App流程,签名证书、IPA 校验、上传
android·ios·小程序·https·uni-app·iphone·webview
2501_916007478 小时前
iOS 上架需要哪些准备,围绕证书、描述文件和上传方式等关键环节展开分析
android·ios·小程序·https·uni-app·iphone·webview
2501_915106328 小时前
iOS 上架费用解析,哪些成本可以通过流程优化降低。
android·ios·小程序·https·uni-app·iphone·webview
小离a_a10 小时前
uniapp微信小程序实现拍照加水印,水印上添加当前时间,当前地点等信息,地点逆解析使用的是高德地图
微信小程序·小程序·uni-app
前端小雪的博客.11 小时前
uniapp小程序顶部状态栏占位和自定义头部导航栏
小程序·uni-app