http ON!!


换了腾讯云服务器、由于IOS强制使用http,太阳城亚洲开户故顺手一同把当前WP站「雪箭微薄」也配置好并记录如下。

STEP 1

更新 wordpress 版本至 wordpress-4.9.4-zh_CN

STEP 2

在服务器上配置好http证书。现在一般的服务商 (百度云、阿里云、腾讯云、七牛云等) 都会免费提供?赛门铁克(TrustAsia) http证书。自己折腾服务器又不想折腾的话可以考虑使用 AMH 、WDCP之类的面板进行操作。

配置好证书后就可以使用 http 打开网站了。但此时会由于多重重定向的问题导致登录不了WP后台。解决方法:

  • 使用 http 方式登录后台 并到 [设置 — 常规] 中将 WordPress地址(URL)和 站点地址(URL)修改成 带http的URL。
  • 下载并打开WP站点根目录下的?wp-config.php 文件 (一般由WP安装后自动生成) 在 MySQL 设置前面添加支持启用http的php代码:
/**
 * WordPress基础配置文件。
 *
 * 这个文件被安装程序用于自动生成wp-config.php配置文件,
 * 您可以不使用网站,您需要手动复制这个文件,
 * 并重命名为“wp-config.php”,然后填入相关信息。
 *
 * 本文件包含以下配置选项:
 *
 * * MySQL设置
 * * 密钥
 * * 数据库表名前缀
 * * ABSPATH
 *
 * @link http://codex.wordpress.org/zh-cn:%E7%BC%96%E8%BE%91_wp-config.php
 *
 * @package WordPress
 */

// 支持 http BEGIN
$_SERVER['http'] = 'on';

define('FORCE_SSL_LOGIN', true);

define('FORCE_SSL_ADMIN', true);
// 支持 http END


// ** MySQL 设置 - 具体信息来自您正在使用的主机 ** //
/** WordPress数据库的名称 */

 

不出意外的话现在可以使用 http 登录后台咯。おめでとうございます。

诶~浏览器打开别人的http站点时左上角是有一把绿锁作为安全提示的、而你的站点却是一个警告图标。点击图标可以看到提示: 您与此网站之间的并非安全 Bala Bala~~~

这是为什么呢?哦,原因时当前 http 网页 引入了 http 连接。比如图片连接、文字外链等。可以右键鼠标查看网站源码,搜索: http:// 会发现当前网页这引用了非 http:// 开头的连接。

STEP 3

解决方法、还我绿锁。

以下操作仅供参考。解决方法就是批量的将数据库中的 http: 替换成 http:。替换前应该先考虑一个问题:数据中可能包含了非http的外部连接(尤其是文章和评论内容中)、且不能够保证这些外链都支持http。所以其实并不推荐直接使用替换法、又或者说根本就没有完美的决解方案。如果还是想尝试的话请务必想备份好数据库

将 wp_options 后台设置字段值中的 http: 替换成 http:

update wp_options set option_value = replace(option_value, 'http:', 'http:');

将文章内容中的链接 http: 替换成 http:

update wp_posts set post_content = replace(post_content, 'http:', 'http:');

将用户信息链接 http: 替换成 http:

update wp_usermeta set meta_value = replace(meta_value, 'http:', 'http:');

附加

可能还需要的步骤。

  • 将主题中的 style.css 中的 Theme URI 和 Author URI 手动修改为以 http: 开头的绝对链接。
  • 将主题其余文件中可能包含 http: 都修改成 http:。

终于绿色小锁回来了。

IOS刷新表格保持位置不变

类型于QQ微信聊天记录: 下拉刷新请求到新数据后,将数据拼接到最面非最面。若此时刷新表格,会发现 tableView 已经自动滚动到了顶部。有没有办法使表格刷新后仍然保持在原来的位置不变呢?且看实现方法。

  • 刷新表格前先记录表格的实际高度
  • 刷新表格后使用新的表格高度减去旧高度得到本次增加的实际高度
  • 设置表格偏移值 等于本次增加的实际高度加上表格原有的偏移值

Swift代码:

let height = tableView.contentSize.height        
tableView.reloadData()            
tableView.contentOffset.y = tableView.contentSize.height - height + tableView.contentOffset.y

QQ互联登录升级

由于QQ互联登录功能的回调地址进行了升级,所以使用雪箭主题并且启用了QQ登录功能的网站也需要作相应的处理。具体方法如下:

新建应用

QQ互联删除原来注册的应用,再重新注册一个新的应用。填写内容示例:

  • 网站地址 : tycyzkh.nba18.net
  • 网站回调域 : http://tycyzkh.nba18.net/wp-content/themes/weibo/extends/Connect2.1/callback.php

注意: 网站地址不带 http(s)。网站回调域请修改成你的域名并把链接中的 weibo 修改成PC版的主题名称。如域名是 www.abcd.com,使用的是 jumei 主题 修改后:?http://www.abcd.com/wp-content/themes/jumei/extends/Connect2.1/callback.php

修改设置

到后台 XPanel 设置中的授权登录项中修改?APP ID 和?APP KEY。

修改文件

在主题目录中找到文件 extends/Connect2.1/callback.php 打开,将第二行删除并在第4行添加一行:

defined('XUEJIAN') || exit('ACC denied'); // 删除这行
if (isset($_GET['state'])) {
require( dirname(__FILE__) . '/../../../../../wp-load.php'); // 插入这行 后面代码不需要修改

在主题目录中找到文件 extends/Connect2.1/config.php 打开,并对第11的回调地址进行修改:

//define('CALLBACKURI', $home_url); // 修改前
define('CALLBACKURI', 'http://tycyzkh.nba18.net/wp-content/themes/weibo/extends/Connect2.1/callback.php'); // 修改后
// 注意回调地址一定要填写与QQ开放平台的地址一致

注意: PC主题和手机版主题修改后的回调地址都是一样的。 修改完成后别忘记使用更新覆盖。

附已修改好的文件下载?请将 config.php 文件中的回调地址修改成QQ互联中填写的地址并上传覆盖。

IOS 自定义滚动条颜色

IOS 为滚动视图提供了黑色和白色两种颜色设置

tableView.indicatorStyle = .black

那么如何自定义滚动条颜色呢?

可以在 viewDidLayoutSubviews 子控件布局完成后,遍历 UITableView 或 UIScrollView。找到滚动条图片视图 UIImageView,并将图片移除设置自定义背景颜色或直接修改图片都可以。

   

 /// 控件布局完成
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        for v in tableView.subviews {
            if let v = v as? UIImageView {

                v.image = nil // 移除图片
                v.backgroundColor = UIColor.lightGray // 背景颜色
                v.layer.cornerRadius = 2 // 圆角
            }
        }
    }

IOS 后台执行 Timer 任务

IOS后台任务

在 IOS 中一旦 App 进入到后台,主进程就会被挂起。若想程序进入后台之后还能够继续执行任务,可以在 AppDelegate 中的 applicationDidEnterBackground 方法监听到应用进入后台之后,请求延时执行一段后台任务。同时程序主进程不会被挂起,这样就达到向 IOS 借时续命的目的。

注意:这个延时后台时间有一定限制:每次请求到延时的时间为 180 秒,最高为 600 秒 (可以使用递归或定时器请求法)。

SWift CODE

    func applicationDidEnterBackground(_ application: UIApplication) {
        
        // 开始执行后台任务 [如有具体任务可在 Block 块中执行]
        var backgroundTask = application.beginBackgroundTask(expirationHandler: nil)
        
        // 后台任务执行时间 [倒序 每次请求 180 秒]
        print(application.backgroundTimeRemaining)
        
        // 延时 10 秒
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 10) {
            
            // 结束后台任务
            application.endBackgroundTask(backgroundTask)
            
            // 销毁任务标识
            backgroundTask = UIBackgroundTaskInvalid
            
            // 查看任务 [0 结束成功]
            print(backgroundTask)
        }
    }

后台无限续时

实践测试可利用 音频播放 + Timer 多次轮询执行请求来达到无限续时的场景。每次请求时播放一句音频是因为播放功能自带后台执行特性, 一旦播放计时就会重置。于是就可以无限请求了。

需求分析

写在后面的需求。

应用中使用了AV系列的播放器:视频播放、音频播放、文字朗读、录音功能。原本只要启用后台播放功能AV系列播放器就可以支持后台播放。但由于本应用需要定时、定位、延时、速率播放、句中切换朗读模式等功能,精确度非常高。普通播放方式无法满足需要。于是——

Timer 是一个时钟定时器。Timer 可以实现以上所有的功能。 Timer 无法单独在后台执行。一般情况下由于AV播放器的后台特性可以保证 Timer 不被销毁。 但也可能遇到特殊的场景:

  • AV播放器播放完一句处于停播状态。
  • Timer 恰好在同一时刻延时等待中。

其结果是: AV播放器释放后台播放进程,Timer 没有了依附也只能被挂起,自然就无法再继续后台播放了。为了决解这个问题使用的正是文首的借时续命法。

决解 UITableView 下拉刷新抖动问题

做下拉刷新的时候需要将数据拼接到表格的末尾,然后再 reloadData 刷新一次表格。但是刷新表格时可能遇到 UITableView 抖动闪屏的情况,这是因为设置了预估行高或IOS11中自动启用了 Self-Sizing。决解的方法是将预估行高设置为零:

tableView.estimatedRowHeight = 0

关闭预估行高后闪屏抖动问题虽然得到了决解,但如果原来使用了 autolayout 自动计算 Cell 行高功能将失效。此时需要使用传统的方式手动计算行高。

UITextField 判断内容是否为空

UITextField 是一朵奇葩的存在,你无法通过 isEmpty 或 count [OC里面用 length] 方法来判断输入框内容是否为空格内容。isEmpty 始终返回 true,? 而 count 默认会计算空格数量。

textField.text!.isEmpty
textField.text!.count

那么…终极的决解方案是,主动先把空格去掉再进行判断字符的输入数量是否等于 0,SWift:

textField.text!.trimmingCharacters(in: CharacterSet.whitespaces).count == 0

Navicat Mysql 中文乱码解决

在使用?Navicat 连接 Mysql 遇到一个奇怪的问题:通过 php 插入数据时如果包含中文,在?Navicat 客户端中就全部变成了乱码 ?? 。但是通过浏览器打开 phpmyadmin 显示是正常的,这说明创建数据库时设置的字符集是正确的,那就是?Navicat 设置的问题了。经过一番摸索最终的解决方法是:

删除旧连接。新建一个 Mysql 连接且不需要设置编码,即保持为默认编码 [自动],中文就能显示正常了。

navicat

IOS Keychain跨应用获取用户密码

Keychain

之前使用过一个手机学习应用:发现他家的两个应用使用的是同一个账号密码,且登录其中之一后再打开另一应用时,能够自动识别并登录。实现这种功能需要使用用到 Keychain 密码管理系统。Keychain 会将用户信息储存到手机系统中,并非应用的沙盒中。所以它安全性比较高,而且还可以利用?Keychain Group 实现数据共享。。

Keychain使用

SecItemAdd? 增

SecItemDelete 删

SecItemUpdate 改

SecItemCopyMatching 查

什么!觉得很熟悉的样子吗?没错、其实也是操作 Sqlite 数据库。下面是直接上 Swift4 支持的 代码。比较新也比较简洁。可以封装起来适当修改以适用自己的项目。

SWift Code

        // 拼接
        let item = [
            // Item 类型 共五种 [通用密码 互联网密码 证书 密钥 身份] 不同类型有不同的字段 不能混用
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: "xuejian", // 账号
            kSecValueData: "123456".data(using: .utf8, allowLossyConversion: true)!, // 密码转成 Data 类型
            kSecAttrLabel: "uuid" // 用户唯一标识符 [可以使用 IDFV 或自行生成] 若不考虑识别手机可以不需要填写
        ] as CFDictionary
        
        // 0. 查询是否已经存在 [false 不存在 true 存在]
        //print(SecItemCopyMatching(item, nil) == noErr)
        
        // 1. 保存 Item 并打印是否操作成功 [true 表示添加成功 以下相同]
        //print(SecItemAdd(item, nil) == noErr)

        // 2. 更新密码
        let updateItem = [
            kSecValueData: "12345678".data(using: .utf8, allowLossyConversion: true)!
        ] as CFDictionary

        print(SecItemUpdate(item, updateItem) == noErr)

        // 3. 删除 Item
        // print(SecItemDelete(item) == noErr)

        // 4. 获取 完整信息
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecReturnData: kCFBooleanTrue,
            kSecReturnAttributes: kCFBooleanTrue,
            kSecMatchLimit: kSecMatchLimitOne
            // kSecMatchLimit: kSecMatchLimitAll // 返回所有数据 [允许一台手机多个账号时会使用到]
            // kSecAttrAccount: "User Account",  // 指定用户名 [同上]
            ] as CFDictionary

        var result: AnyObject?

        let status = withUnsafeMutablePointer(to: &result) {
            SecItemCopyMatching(query, UnsafeMutablePointer($0))
        }

        if status != noErr {
            print("Keychain Item 不存在!")
            return
        }

        guard
            let existingItem = result as? [String : AnyObject],
            let passwordData = existingItem[kSecValueData as String] as? Data,
            let uuid = existingItem[kSecAttrLabel as String] as? String,
            let account = existingItem[kSecAttrAccount as String] as? String,
            let password = String(data: passwordData, encoding: String.Encoding.utf8)
        else {
                return
        }

        let dict = ["account": account, "password": password, "uuid": uuid]

        print(dict)

数据共享

拷贝同一份项目并将 Bundel id 修改成:

  • com.xuejiancn.keychain1
  • com.xuejiancn.keychain2

在?Capabilities 中开启 Keychain Sharing 共享功能并将两个项目的 Keychain Groups 都设置成同一个组:

  • keychain
  • com.xuejiancn.keychain1

最后就可以在 App1 存储数据,然后到 App2 读取数据,实现共享了。

UIView 接收并区分单击和双击事件

奇怪的需求。视图中既有单击事件同时又有双击事件。如果不做特殊的处理太阳城亚洲开户双击时两个事件都会触发。那么该如何进行区分呢?
其实可以 在 touchesBegan 方法中拦截事件并根据点击的次数进行相应的操作。

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        
        // 取出点击事件
        let touch = touches.first!
        
        // 判断点击次数
        if touch.tapCount == 1 {
            // 延迟执行任务
            perform(#selector(tapGesture), with: nil, afterDelay: 0.2)

        } else if touch.tapCount == 2 {
            
            // 取消上个单击事件
            NSObject.cancelPreviousPerformRequests(withTarget: self)
            // code
        }
    }

扩展:假如在 UITableViewCell 上的话由于获取不到点击事件,可以使用计数器变量的方式记录点击次数。

IOS 实现模型分组

你是否遇到需要对模型进行分组的需求,就是将模型数据按照一定的条件进行筛选。下面是一个将书模型进行分组的例子。

已知数组中存放着对书籍描述的信息模型,书名、第几册、级别等相关信息。那么如何将这些书模型按 [书名] 分组抽取出来到一个二维数组中呢?

刚开始从网上找到了使用 NSMutableSet 和 NSPredicate 结合筛选出数组的方法。的确也将模型分组好了,但却确存在一个小问题,数组排序乱了。得。 还得重新排序吧。

于是,想了想换个思路8。最后通过直接遍历模型的方式,略施小技巧就实现了完美分组。献上 Swift Code:

// 模型数组
        var books = [[NPMenuModel]]()
        
        // 分组 id
        var groupId = 0
        
        // 遍历模型
        for (i,model) in models.enumerated() {
            
            // i 等于 0 时直接将模型追加到大数组 并跳出本次循环
            // 此时已经有了第一个分组
            if i == 0 {
                // 相当于创建了第一个分组
                books.append([model])

                continue
            }
            
            // 取当前模型 字段值 与上个模型的 字段值 做比较 [字段值就是分组的标识 通常是某个字符串]
            // 如果比较结果相等就将模型追加到旧分组中
            if model.book == models[i - 1].book {
                books[groupId].append(model)
            } else {
                // 比较结果不相等就新建一个分组 并记录分组 id
                groupId += 1
                books.append([model])
            }
        }

IOS子线程刷新 UI 警告: This application is modifying the autolayout

This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.

AVPlayer 加载网络数据时会卡顿到主界面的 UI, 为了解决这个问题开启了子线程异步执行。但是 XCode 控制台中却打印了上述的警告。这是因为子线程中刷新了主界面的 UI。解决方法是回到主线程再刷新就没问题了。

SWift 4 Code:

// 开启子线程 防止播放器卡顿 UI
DispatchQueue.global().async {
    // 很多代码

    // 回到主线程刷新 UI
    DispatchQueue.main.async {
        // 刷新 UI 代码放这里  包括计算 frame 布局也应该在这里面
     }
 }