解决SwiftUI中的透明view点击无法生效的问题

透明的view,无论是整体opacity设置为了0,还是颜色设置为了clear,在swiftUI中,默认的content shape (内容形状)就是0,所以点击事件是无法生效的,而要解决这个问题,我们只需要手动设置content shape即可 代码如下:
Color.clear
  .frame(width: 300, height: 300)
  .contentShape(Rectangle())     // 这里,当然上面的frame也有必要,如果frame为0,则肯定也无法触发点击
  .onTapGesture { print("tapped") } 

SwiftUI中如何自定义Navigation返回按钮

iOS15+

// 自定义的返回按钮,按照你的需求自己定制
struct NavBackButton: View {
    let dismiss: DismissAction
    
    var body: some View {
        Button {
            dismiss()
        } label: {
            Image("...custom back button here")
        }
    }
}

// 在要使用的view下,加入如下modifier
.navigationBarBackButtonHidden(true) // Hide default button
.navigationBarItems(leading: NavBackButton(dismiss: self.dismiss)) // Attach custom button
在引入了上述逻辑后,就会在child view中显示你的自定义的按钮,但同时,左滑返回的手势会失效,如果你需要保留这个手势,则还需要加入如下的代码:
// 让使用自定义返回按钮时,左滑返回的动画不失效,同时对child view是scrollview等也需要监听drag gesture的情况做了兼容
extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }

    // To make it works also with ScrollView
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        true
    }
}

iOS15之前

struct SampleDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
            Image("ic_back") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.white)
                Text("Go back")
            }
        }
    }
    
    var body: some View {
            List {
                Text("sample code")
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}

SwiftUI 如何让view强制支持横屏或竖屏

问题:有时候在编写APP时,我们会需要某个页面只支持手机的某个方向(并不是懒得适配,确信)。而在swiftUI中,要实现这个设定又变得更加复杂了,本篇文章就希望提供一个清晰且简单的解法,来支持这一点。

首先,我们需要swiftUI支持AppDelegate.

在非swiftUI中,这个文件是在创建项目的时候就会被系统一同创建的,但swiftUI并没有为我们创建这个文件,但这并不意味着swiftUI就无法连接到AppDelegate并作出响应

而其实建立连接也十分简单,首先,创建一个class,这里其实创建AppDelegate的任何方法都可以,但为了不重复赘述,我这里直接写好了后面屏幕方向设定会用到的方法

class AppDelegate: NSObject, UIApplicationDelegate {
static var orientationLock = UIInterfaceOrientationMask.portrait

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -&gt; UIInterfaceOrientationMask {
return AppDelegate.orientationLock
}
}

这个设定,是将APP支持的屏幕方向设置了只支持竖屏,当然,如果你的APP也仅仅想做到这一步,那其实在设置里也可以直接做到,不用专门写一个class

接下来,在某个我们希望只支持横屏的view中,只要写下如下代码,即可切换到横屏

import SwiftUI

struct DestinationView: View {

var body: some View {
Group {

Text("Hello")

}.onAppear {
AppDelegate.orientationLock = UIInterfaceOrientationMask.landscapeLeft
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
.onDisappear {
DispatchQueue.main.async {
AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
}
}
}

要注意的是,UIDevice中设定Value时要使用rawValue,否则会报错,而在onDisappear方法中,要使用异步来保证当前view消失时,不会因为之前的view的屏幕朝向不同而报错

SwiftUI CoreData entity/实体更新导致无法预览的问题

最近改动了某个entity的类型,取消了它的继承,preview突然就停止工作了,一直提示crash,直接调用以下方法,清除之前的preview设定即可,因为preview里还保存了旧的设定,与新设定冲突,导致preview crash

xcrun simctl --set previews delete all

iOS开发如何浏览模拟器中的coredata数据

在后端开发使用go语言时,网络包的使用经历了beego,fasthttp等包,最终又用回了go的官方http包(当然这个包最近各种升级例如支持了优雅退出啊等等就算是另一说了),发现还是尽量用官方的东西更好一些。

iOS开发也是这样,iOS开发中本地数据库操作也有不少的解决方案,从iOS自带的coredata,到FMDB,再到完全不依赖sqlite的Realm,我原本也使用了一段时间的realm,但有两点让我很不爽

  1. 毕竟是第三方项目,总是要更新,如果你是使用OC语言的,那么还好,但如果你是像我一样使用swift语言的,那么swift的更新外加realm自身的更新能够把你烦死
  2. realm对于字段变更的操作非常不友好,如果还没生成数据还则罢了,如果已经生成了数据,那么不好意思,请按照realm提供的反人类一样的方法一步一步执行,而且你还得关注当前的字段版本,比如最老的版本是1.0,之后你又推出了1.0.1和1.0.2的更新,那么你就至少得写下1.0到1.0.2和1.0.1到1.0.2的两个版本迁移的代码块,因为我们是在面向C端的,你永远不知道你的用户什么时候更新你的APP,所以你得提供所有老版本到最新版本的转化方式。

综上,最终我还是决定用回coredata,至少官方的支持还是可以的,swift版本也不用像OC一样需要几个步骤生成连接代码等等,直接在model文件里操作好,背后的一切工作swift都已经帮你完成了,当然,这里我要强调一点,那就是本地数据库并不应该存储多么巨大复杂的数据,如果一个用户一天在你的APP里生成10条数据,一年也不到4000条,完全到不了需要拼速度和优化的时候。

终于说到正题,既然使用了coredata,那么调试的时候免不了会想要查看一下数据或者手动更改数据,可是模拟器的数据一是藏得深,二是并没有直接浏览更改的方式,我们分别解决一下。

获取数据路径

在AppDelegate文件的didfinishlaunch中加入下面这一行代码

print(FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask))

这样会把当前模拟器的文件路径打印出来,有了路径,就好办了,比如可以在terminal里直接open,然后应该会看到一个名为“Application Support”的文件夹,里面就是对应的.sqlite文件

查看.sqlite文件

有了文件,如何查看又是个问题,下面提供以下几个方法,可以根据自身情况自由选择

  • (推荐)使用sqlite3直接在terminal里查看
    • 对于习惯于用命令行操纵MySQL的人来说,sqlite3的操作应该没有什么入门难度,也是比较推荐的方法,Mac又是自带Python和sqlite3的,如果找不到,那么可以从如下网站来进行安装:https://www.sqlite.org/download.html
  • (推荐)可视化方案:DB Browser for SQLite
    • 对于习惯于可视化管理的人来说,我推荐这个方案,本身是一个在GitHub上的开源项目,马上要突破1万星,安全和可靠性都有保障,还是免费的,只要把.sqlite文件拖到这个APP内就可以自动打开,当然coredata自动命名的表名和你自己命名的略有区别,比如我创建了一个Objectives的Enity,在这里看到就是ZOBJECTIVES,自己找一下就可以了
    • GitHub地址:https://github.com/sqlitebrowser/sqlitebrowser
    • 官方网页直接下载打包好的版本:https://sqlitebrowser.org
  • 可视化方案:Base 2
    • 功能更完善,界面更好看,但没有优先推荐的原因是其是收费的,20欧的售价其实并不算高,不过没有特别专业复杂的需求,用上面的就足够了
    • 官网地址:https://menial.co.uk/base/
  • 可视化方案:Firefox sqlite插件
    • 这个大概就是特定适合使用火狐浏览器的人群了吧,我本身不用火狐,所以各位如果用的也可以测试了给我反馈一下
  • (不推荐尝试)可视化方案:Core-Data-Editor
    • 同样是GitHub上的一个开源方案,但由OC写成,同时也只支持OC,同时需要有一个初始的适配过程,并且不能直接打开.sqlite文件
    • GitHub地址:https://github.com/ChristianKienle/Core-Data-Editor

CocoaPods 1.1.0无法更新老项目中原有Target的解决办法

更新了Xcode8之后,重新打开好久没维护的一个项目,结果发现Alamofire4.X和iOS10以前的版本并不兼容,一狠心,不兼容9.X系列了。

安装Alamofire4.X需要CocoaPods 1.1.0,更新了pod之后我发现执行pod install的时候一直提示我的extension target需要host target,Google了各种解决办法也都没有解决,最终尝试了stackoverflow里看到的一条评论的方法:将老的target删了之后再重新添加,最终解决问题。。

应该算个比较笨的方法吧,不过也算是解决了问题。

Xcode8中无法设定extension图标的解决办法

更新了Xcode8之后一直在忙公司的项目,现在才得空照顾下自己的APP,我的APP有一个action extension的Target,先是pod抽风安不上库了,后来又发现图标找不到了。后来发现原来设置图标的位置被苹果转移了

wechatimg7

没错,转移到了Build setting里了。。

但是我在找到了解决方法之后又遇到了问题,就是根本没有这一项啊!!后来经过我自己摸索,发现需要先在Copy bundle resources里面加入你存有图标的Asset,然后这一项会自动出现。。

真是服了Apple的程序员,是觉得以前设置图标的方式不符合探索精神么。。

Swift的get与set与willSet与didSet详解

最近写程序越来越会偷懒了,可能也是因为对swift越来越熟悉了吧,以至于最近连if都不想写了都直接用三元运算符了。。这也是今天这篇文章的导火索,就是因为我有两个布尔变量,来判断是否是pulling和headPulling(简称p和hp),hp为true时p一定也为true,但p为true时hp就不一定为true,因为还会有footPulling的情况,你问为什么不是三个布尔变量?因为我懒啊!但我还想更懒,我完全不想去管p,而是想只改hp就可以完全满足我判断究竟是fp还是hp以及p的情况,该怎么样呢?于是我就想起来了以前看到过的get和set,但是虽然抄了过来,但具体意义什么的完全没深究过,这次就研究了一下!

 swift的计算属性

Google了一番才知道get和set这两位的术语是:Computed Properties. 即计算属性。其实从名字就可以看出来,其真正的作用是计算,而非存储。不少从OC转来的开发者可能一开始会将其按照原本的getter和setter方法来理解而导致一些疑惑。计算属性真正的用处是在于利用这两个方法间接的获取或者改变其他的变量或属性。

get

get方法在这两个方法中是必须的,意即如果要声明计算属性,则必须存在get方法,get方法的作用是在开发者用dot文法获取该变量的值的时候,返回一个固定的或经过计算的值。

set

set方法是一个可选方法,其会传入一个与所属变量类型相同的参数,这个参数的值即使用中被赋予的值,在set文法中,可以利用获取的参数的值,去计算并设定其他变量的值。如果要使用set方法,则必须包含get方法。

以下是官方开发者文档中给出的例子:

struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

其实只要稍微仔细看一下就可以理解get和set的用法,所需要注意的就是如下几点:

1. 无论是set还是get都不可以改变(或返回)自身的值。这个错误不会被编辑器发现,但会是运行时错误而被抛出异常。

2. set可以忽略参数声明而直接使用newValue的默认值,如上面的例子可以被改写成这样

set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}

3. 如果要声明计算属性则必须声明get方法,只声明get方法的计算属性成为只读计算属性,将来的使用中该属性无法被赋值而只能获取其在get方法中返回的值。同时,只读计算属性可以简化写法,不用再按标准方式声明get方法。例如:

struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

以上便是计算属性的用法了,可是仔细一想就能发现,我其实需要的并不是计算属性,尽管我确实是想在我的hp改动的时候联动的改动p,这是计算属性中的set方法可以做到的事情,但是同时我并不需要get方法,但恰恰get方法又是计算属性所必须的,所以再深究一下就能发现我们这次的正题:willSet和didSet。

属性观察者

正如其名,willSet和didSet这两个方法会观察属性的值的改变,并在特定的时候做出特定的反应。每一次属性的值的改变都很调用设定好的属性观察者的方法,就算新的值和属性越来的值相同也不例外。

除了定义为lazy的存储属性外,这两个观察者方法可以在任何其他存储属性中声明。对于继承的情况,观察者方法同样适用于继承的存储属性,同时还增加了继承的计算属性,当然,你不用为不重写的父计算属性添加属性观察者,因为你可以直接在set中做到观察者所能做的事情。

同时需要注意,这两个方法之间没有必然的关系,你可以同时声明这两个方法或只声明其中任意一个。

但是这两个方法的调用时间和传入参数却有很大不同:

willSet方法会在新值被储存前调用,官方文档中使用了“just before”这个词,可能意思就是这个方法调用完了该变量就会立刻存储新值吧。同时willSet方法接收的参数是即将被存储的新值,即newValue,如果希望简化方法的话可以不书写参数而直接用dot文法调用该变量。

而didSet方法会在新值被存储后调用,官方文档中使用了“immediately”这个词,同上的猜测就是这个方法会在存储完新值后立刻调用。与willSet不同的是,didSet中的参数是改变之前值,即oldValue,同样允许简化书写。

以下是官方的例子:

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

这个方法就可以完美解决我遇到的问题,我只要在hp的willSet观察者方法里直接设定p的值就好了,有关get和set以及willSet和didSet也就说得差不多了,这两个知识点并不是什么难点,但感觉真正会实用的人也是比较少的,所以特来分享一下。

iOS求道录 – swift开发问题总结 – 1

从最开始学习iOS开始,就习惯把遇到的问题记录下来,不知不觉也积累了不少,我相信也许其他人也会遇到这样那样的问题,可以大家拿出来一起分享,或者有问题我们一起解决,或者觉得我的解决方法存在问题,也欢迎指正。每个篇文章更新10个问题。若评论中存在好的问题,我也会在征求评论作者同意之后更新到文章之中。另外抱歉起了个比较中二的名字,其实感觉不会有那么厉害。(:з」∠)

我就按照我记录的顺序来了,因为是刚刚入门时候就开始记录了,所以可能前面的问题看起来都会比较幼稚

1.如何修改Tabbar item的selected color

首先是简单的方法,整体修改选中后颜色,在随便哪个tabbar的子VC里使用如下代码都可以修改其选中后颜色,下面修改为蓝色

self.tabBarController?.tabBar.tintColor = UIColor.blueColor()

而如果是希望不同的item使用不同的选中后颜色,则可以利用storyboard这样快捷实现(前提是你利用storyboard实现TabbarVC。。)
选中某个tabbaritem之后,在有侧边栏里这样修改:

blog-1

2.判断字符串之间关系

利用rangeofstring函数,其会返回一个布尔变量,示例:

var myString = "This is a string test"
if myString.rangeOfString("string") {
    print("exists")
}

3.将控件的默认语言修改为中文

点击工程名→寻找Info选项→在其下拉栏中寻找localization→将其value修改为china

4.tableview cell无法正常显示

这个真的是初学者问题,还弄了半天,也是当时的Xcode有点小问题,所以浪费了很多时间,就是tableview的delegate的有关section和row的数量的两个函数的返回值要设为非0,当时的Xcode版本,如果改变numberOfSection这个函数的返回值(在TableviewController中,已经默认提供了这两个函数且返回值均为0)为非0,则会报错,但是这个错误在点击运行之后就会消失,当时并不知道,看到报错以为不能改,就改回0,然后各种百度谷歌也找不到答案,还找到了很多奇葩的理由来解释自己的错误,现在看看好蠢。

5.在storyboard中关联class文件的问题

在storyboard中拖入的是什么controller(例如TableviewController),其所关联的class就必须是该类型的view controller,否则在使用segue跳转是会报错并导致程序崩溃。

这个也是早期的问题了,现在我使用storyboard的话所有的controller都是基础的UIViewController,因为遇到过很多问题,即比如如果使用UITableViewController无法正常显示广告条的问题。

6.如何添加storyboard辅助线

storyboard中,双击某个view,再按shift+command+ -会添加横向的辅助线,shift+command+ | 会添加纵向的辅助线。删除辅助线的方法就是点击辅助线后快速移出view到旁边的空白处

7.引用protocol时报错

主要是在关联delegate和datasource时发现的,以前遇到这种情况通常都去搜索引擎了,后来发现自己可以很轻松解决:

这种情况是你关联了这两个对应的数据源,却没有实现他的回调方法,像tableview和pickerview等等都需要调用特定的回调方法才会不报错。

在这种情况下的方法,就是:右键点击datasource →jump to definition→寻找该方法下不带option标签的func→复制这些func到view controller,实现之,即可。

以上是当时写的解决办法

8.将Int等其他类型的变量变为字符串输出

当时真的是很郁闷,我本身是学软件出身的,但完全不记得转义符这个东西。。。找了很多办法想把一个int在swift里转化为string来输出,最后突然想起了转义符,然后就没有然后了。。

“\(times[row])”,这样就可以输出原本为int数组的times中得值了。

当然现在也知道了format函数的用法,但就不在这个问题下面讨论了

9.读取与写入plist的方法

var partArray : NSArray?   //声明存储plist文件数据的数组
let ban = NSBundle.mainBundle()//获取mainbundle的值,这里面保存了plist文件的地址(注意plist文件必须创建在主文件路径下才可以)
        let plistPath = ban.pathForResource("myNumber" , ofType : "plist”)//连接plist文件
        let partFromP = NSMutableDictionary(contentsOfFile: plistPath!)//将plist文件存入一个数组
        partArray = partFromP?.objectForKey("肩部") as? NSArray  //通过key值寻找值

早时自己写着玩的时候,遇到了数据存储问题,当时觉得coredata太麻烦,于是用plist来做存储。。现在发现coredata会了之后比plist的存储简单太多,毕竟要用plist来存东西是要自己时刻记着数据的表结构的。。
不过还是提供一下关于plist写入的方法:

IOS设备写入plist文件时,不能使用mainbundle来寻找文件路径,这样只会导致写入失败,以下是正确地寻找文件路径的方法:

let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
//以上三行获取文件地址就不细说了,下面这行是将文件中的值保存到一个NSDictionary中
let mydict = NSDictionary(contentsOfFile: path)
//获取用户数组
var partArray : NSArray = mydict?.objectForKey("users") as! NSArray
//需要新加的数据
var object = [NSDictionary]()
object.append(["part" : "上臂" , "move" : "肩部挺举" ,"group":"3","times":"15"])
//返回一个新的数组
partArray =  partArray.arrayByAddingObjectsFromArray(object)

10.NSArray的arrayByAddingObject方法解释

这个方法会返回一个新的数组,而原数组未改变,所以可以这样更新原数组

partArray1 =  partArray1?.arrayByAddingObject(object)

A new easy way to Screenshot&Share

Put a website in your Favorites or Bookmark? Now you can put it in your photos!

  • Support both website and markdown files

    Easy share present your extremely easy way to get a screenshot from a website or even markdown files. Open the website or markdown file and click the button and you will get your screenshot

  • Full size screenshot

    The screenshot you take will be full size of the website you opened, so you can share with your friends on social apps, or if you want, save to your photos so the next time you want to review the website you won’t have to open your safari and search the bookmark.

  • Efficient ways to open a URL

    We present many easy ways for you to open a URL. You can scan a QR code with a URL. Or you can input the address manually. What’s more, if you copy a URL from safari or some other apps, you don’t have to paste it, our app will recognize it automatically and auto paste it for you.

  • Efficient markdown editor

    We also present a efficient markdown keyboard for users who want to write a markdown article. And of course several markdown themes you can switch easily

 

  • The url you copy should be start with ‘http://’ or ‘https://’